
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.
// Quick syntax
_.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);
// Result: { theme: "light", editor: { fontSize: 18, lineNumbers: true } }
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
- Lodash Merge Syntax and Parameters
- Merging Two Objects (Top-Level Merge)
- Deep Merge Nested Objects with Lodash
- Lodash Merge Arrays — How It Actually Works
- Customize Merge Behavior with _.mergeWith()
- Lodash Merge vs Object.assign vs Spread Operator
- Practical Examples of Lodash Merge
- Lodash Merge with TypeScript
- Common Pitfalls and Edge Cases
- Performance Considerations
- Native Alternatives to Lodash Merge in 2025
- Related Lodash Guides
- FAQs
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:
// ES6 Module — import only merge (tree-shakable)
import merge from 'lodash/merge';
// CommonJS
const merge = require('lodash/merge');
// Or import the full library
import _ from 'lodash';
// then use _.merge()
Lightweight alternative — install only the merge module:
npm install lodash.merge
import merge from 'lodash.merge';
Tip: Importing
lodash/mergeinstead of the fulllodashlibrary can reduce your bundle size significantly. The full Lodash library is ~70KB (minified + gzipped), whilelodash.mergealone 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);
// Output: { a: 1, b: 3, c: 4 }
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);
// Output: { a: 99, b: 2, c: 3 }
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);
// Output:
// {
// user: {
// name: "Alice",
// address: { city: "Mumbai", zip: "700001" },
// role: "admin"
// }
// }
Notice what happened:
user.name("Alice") was preserved becauseobject2didn't have it.user.address.citywas updated to "Mumbai".user.address.zip("700001") was preserved — this is the deep merge behavior.user.role("admin") was added fromobject2.
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);
// Output: [10, 20, 3]
Here is what happened:
- Index 0:
1was replaced by10 - Index 1:
2was replaced by20 - Index 2:
3was preserved (no corresponding index inarr2)
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);
// Output:
// [
// { id: 1, name: "Alice", role: "lead" },
// { id: 2, name: "Bob", role: "designer" }
// ]
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);
// Output: { tags: ["javascript", "lodash", "node", "npm"] }
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);
// Output: { math: 90, science: 92 }
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);
// Output: { plugins: ["auth", "logger", "cache"] }
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 } };
// Using _.merge()
_.merge({}, target, source);
// Result: { a: 1, nested: { b: 2, c: 99 } } ← b is preserved ✅
// Using Object.assign()
Object.assign({}, target, source);
// Result: { a: 1, nested: { c: 99 } } ← b is LOST ❌
// Using Spread
{ ...target, ...source };
// Result: { a: 1, nested: { c: 99 } } ← b is LOST ❌
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);
// Output:
// {
// server: { port: 8080, host: "localhost" },
// database: { host: "prod-db.example.com", port: 5432, name: "myapp_prod" },
// logging: { level: "warn", format: "json" }
// }
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);
// Output:
// {
// username: "john_doe",
// bio: "Full-stack developer",
// social: {
// twitter: "johndoe_twitter",
// linkedin: "johndoe_linkedin",
// github: "johndoe_github"
// },
// preferences: {
// theme: "dark",
// notifications: { email: true, push: true }
// }
// }
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);
// users[0] now has all fields: id, name, lastSeen (updated), and online (new)
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" }
};
// TypeScript infers the return type correctly
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);
// Output: { a: 1, b: { c: 2, d: 3 } } ← original is modified!
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);
// Output: { a: 1, b: 3 } ← a stays 1, undefined is skipped
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:
// ⚠️ VULNERABLE in old versions
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
_.merge({}, malicious);
// In vulnerable versions, this could affect ALL objects:
// {}.isAdmin === true
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
Performance Considerations
_.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/mergeinstead 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+)
// Deep clone first, then shallow merge
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
Related Lodash Guides
If you are working with Lodash for data manipulation, these guides cover the other essential methods:
- Lodash _.sortBy() — Sort Objects, Arrays & Descending Order — Learn how to sort arrays of objects by property, multiple fields, descending order with
_.orderBy(), and case-insensitive sorting. - Lodash _.groupBy() — Group Arrays & Objects by Key — Learn how to group data by property, multiple keys, custom functions, and combine with
_.sumBy()for aggregation.
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.
Ready to Implement This in Production?
Skip the months of development and debugging. Our team will implement this solution with enterprise-grade quality, security, and performance.