Skip to content
Lodash Objects & Functions — Deep Clone, Merge & Performance Guide

Lodash Objects & Functions — Deep Clone, Merge & Performance Guide

DodaTech Updated Jun 6, 2026 18 min read

Lodash object and function utilities handle deep cloning, safe nested access, and rate-limiting for performant event handling in real-world JavaScript applications.

What You’ll Learn

  • Object operations: _.clone, _.cloneDeep, _.merge, _.assign, _.defaults, _.pick, _.omit
  • Deep access: _.get, _.set, _.has, _.unset, _.result
  • Function utilities: _.debounce, _.throttle, _.memoize, _.curry, _.partial, _.once
  • Control flow: _.after, _.before, _.negate

Why Objects & Functions Matter

JavaScript objects are everywhere — configuration settings, API responses, user profiles, and application state. But copying, merging, and accessing nested properties in objects is surprisingly error-prone. A single Cannot read property 'x' of undefined error can crash your app.

Lodash gives you safe, predictable tools for these operations. In Durga Antivirus Pro, configuration objects with dozens of nested settings (scan profiles, exclusion lists, notification preferences) are deep-merged with user overrides using _.merge, and debounced auto-save prevents excessive disk writes while users adjust settings.

Learning Path

    flowchart LR
  A["Collections<br/>& Arrays"] --> B["Objects &<br/>Functions"]
  B --> C["Utilities &<br/>Performance"]
  B --> D["Real Project:<br/>Config Manager"]
  style B fill:#38bdf8,color:#0f172a,stroke:#38bdf8,stroke-width:2px
  style D fill:#22c55e,color:#0f172a
  
Complete https://tutorials.dodatech.com/frontend/libraries/lodash/lodash-collections-arrays/ first. You should understand _.map, _.filter and be familiar with JavaScript objects (properties, nesting, references vs values).

Object Operations

_.clone vs _.cloneDeep — Shallow vs Deep Copy

Here’s the core problem with copying objects in JavaScript: when you copy an object, nested objects are shared references, not copies.

Think of it like photocopying a document with a stapled attachment. A shallow copy (level 1) copies only the cover page. The attachment is still the same physical sheet. A deep copy duplicates everything — cover and all attachments.

import clone from 'lodash/clone';
import cloneDeep from 'lodash/cloneDeep';

const original = { a: 1, nested: { b: 2 } };

// Shallow copy
const shallow = clone(original);
shallow.nested.b = 99;
console.log(original.nested.b); // 99 — mutated! They share the nested object

// Deep copy
const deep = cloneDeep(original);
deep.nested.b = 42;
console.log(original.nested.b); // 99 — unchanged, completely independent

What _.cloneDeep handles that JSON.parse(JSON.stringify()) cannot:

Data TypeJSON.parse(JSON.stringify())_.cloneDeep
FunctionsLost (removed)Preserved
undefinedLost (removed)Preserved
Symbol keys/valuesLostPreserved
BigIntThrowsPreserved
Circular referencesThrowsHandled
DateConverted to stringPreserved as Date
Map, Set, RegExp, ErrorLost or convertedPreserved

_.merge vs _.assign vs _.defaults — Three Ways to Combine

All three combine properties from source objects into a destination, but behave differently with nested properties:

MethodDeep?Overwrites?Array Handling
_.assignNoYesReplaces
_.mergeYesYesConcatenates
_.defaultsNoNo (first value wins)First wins
import assign from 'lodash/assign';
import merge from 'lodash/merge';
import defaults from 'lodash/defaults';

const target = { a: 1, nested: { b: 2, c: 3 } };
const source = { a: 10, nested: { b: 20 }, d: 4 };

// _.assign is shallow — nested.c is lost entirely
assign({}, target, source);
// { a: 10, nested: { b: 20 }, d: 4 }

// _.merge is deep — nested.c is preserved
merge({}, target, source);
// { a: 10, nested: { b: 20, c: 3 }, d: 4 }

// _.defaults does NOT overwrite — a stays as 1
defaults({}, target, { a: 99, e: 5 });
// { a: 1, nested: { b: 2, c: 3 }, e: 5 }

When to use each:

  • _.assign: Flat object merging, like combining two simple option objects
  • _.merge: Configuration merging where nested settings from multiple sources should be preserved
  • _.defaults: Setting fallback values without overriding existing ones

_.pick and _.omit — Select or Exclude Properties

These are like whitelist and blacklist filters for object properties:

import pick from 'lodash/pick';
import omit from 'lodash/omit';

const user = { id: 1, name: 'Alice', password: 'secret', ssn: '123-45-6789' };

// Whitelist: only keep what you need
pick(user, ['id', 'name']);
// { id: 1, name: 'Alice' }

// Blacklist: remove sensitive fields
omit(user, ['password', 'ssn']);
// { id: 1, name: 'Alice' }

_.pickBy and _.omitBy — Filter by Condition

import pickBy from 'lodash/pickBy';
import omitBy from 'lodash/omitBy';

const stats = { active: true, score: 85, trial: false, email: 'a@b.com' };

// Keep only truthy values
pickBy(stats, Boolean);
// { active: true, score: 85, email: 'a@b.com' }

// Remove boolean values
omitBy(stats, v => typeof v === 'boolean');
// { score: 85, email: 'a@b.com' }

Deep Access: _.get, _.set, _.has, _.result

_.get — Safe Nested Property Access

This is the single most valuable Lodash method for preventing Cannot read property of undefined errors:

import get from 'lodash/get';

const data = {
  user: {
    profile: {
      name: 'Alice',
      address: null
    }
  }
};

// Safe access — no errors if path doesn't exist
get(data, 'user.profile.name');           // 'Alice'
get(data, 'user.profile.address.city');   // undefined — no crash!
get(data, 'user.profile.age', 18);        // 18 — default value
get(data, ['user', 'profile', 'name']);   // 'Alice' — array path works too

What would native JavaScript look like?

// Without _.get, you'd need this:
const name = data && data.user && data.user.profile && data.user.profile.name;
// Or optional chaining (ES2020+):
const name = data?.user?.profile?.name;

_.get is especially valuable when reading deeply nested API responses or configuration files where some paths may be missing.

_.set — Create Nested Paths Automatically

Creates intermediate objects or arrays as needed:

import set from 'lodash/set';

const obj = {};
set(obj, 'a.b.c', 42);
console.log(obj); // { a: { b: { c: 42 } } }

// Array path notation
set(obj, ['x', 'y', 'z'], 'hello');
// obj is now { a: { b: { c: 42 } }, x: { y: { z: 'hello' } } }

_.has — Check if Path Exists

import has from 'lodash/has';

has({ a: { b: 1 } }, 'a.b');        // true
has({ a: { b: null } }, 'a.b');     // true (null is a value)
has({ a: { b: 1 } }, 'a.b.c');      // false

_.result — Get or Invoke

Like _.get but invokes the value if it’s a function:

import result from 'lodash/result';

const obj = {
  name: 'Alice',
  greeting: () => 'Hello!',
  config: null
};

result(obj, 'name');          // 'Alice'
result(obj, 'greeting');      // 'Hello!' (function is invoked)
result(obj, 'config.path', 'default'); // 'default'

Function Utilities

_.debounce — Wait Until Activity Stops

Imagine you’re typing a search query. You don’t want to send an API request on every keystroke — that would be hundreds of requests per second. You want to wait until you stop typing, then send one request.

That’s exactly what debounce does. It delays invoking a function until wait milliseconds have passed since the last invocation.

import debounce from 'lodash/debounce';

// Create a debounced function
const save = debounce(() => {
  console.log('Saving...');
  // In Durga Antivirus Pro, this would write config to disk
}, 500);

save(); // Timer starts, but function doesn't run yet
save(); // Timer resets — previous call cancelled
save(); // Timer resets again
// After 500ms of no more calls: 'Saving...' runs once

Options:

  • { leading: true } — fire immediately on first call, then debounce
  • { trailing: false } — skip the trailing edge call
  • .cancel() — cancel pending invocation
  • .flush() — immediately invoke if pending

_.throttle — At Most Once Per Interval

Throttle is like a nightclub bouncer who only lets one person in every 10 seconds. No matter how many people show up, only one passes per interval.

import throttle from 'lodash/throttle';

window.addEventListener('scroll', throttle(() => {
  console.log('Scroll position:', window.scrollY);
  // This runs at most once every 200ms, no matter how fast you scroll
}, 200));

Debounce vs Throttle — When to Use Each

ScenarioChooseWhy
Search input autocompleteDebounceWait until user stops typing
Window resize handlerDebounceExecute after resize completes
Scroll position trackingThrottleNeed regular updates during scroll
Button double-click preventionThrottleOnly allow one click per interval
Auto-save in editorDebounceSave after user pauses editing

_.memoize — Cache Expensive Results

Memoization stores the result of a function call and returns the cached result when the same arguments are provided again.

Think of it as a sticky note on your monitor: you calculate something once, write the answer on the note, and next time just read the note instead of recalculating.

import memoize from 'lodash/memoize';

const expensive = memoize((n) => {
  console.log('Computing...');
  return n * 2;
});

expensive(5); // 'Computing...' → 10
expensive(5); // Cached → 10 (no console.log)
expensive(10); // 'Computing...' → 20

Custom cache key (by default, only the first argument is used as key):

const memoized = memoize((a, b) => a + b, (a, b) => `${a}-${b}`);
_.memoize caches indefinitely — memory grows unbounded. For long-lived apps, call .cache.clear() periodically or use a bounded cache (LRU).

_.curry and _.partial — Pre-Fill Arguments

Currying transforms a function that takes multiple arguments into a chain of functions that each take one argument:

import curry from 'lodash/curry';

const add = curry((a, b, c) => a + b + c);
add(1)(2)(3);   // 6
add(1, 2)(3);   // 6 (also works)

Partial application pre-fills some arguments, returning a function that takes the rest:

import partial from 'lodash/partial';

const greet = (greeting, name) => `${greeting}, ${name}!`;
const sayHello = partial(greet, 'Hello');
sayHello('Alice'); // 'Hello, Alice!'
sayHello('Bob');   // 'Hello, Bob!'

_.once, _.after, _.before — Call Count Controls

import once from 'lodash/once';
import after from 'lodash/after';
import before from 'lodash/before';

const init = once(() => console.log('Initialized!'));
init(); // 'Initialized!'
init(); // No output (already called)

const done = after(3, () => console.log('Done!'));
done(); done(); done(); // Only third call logs 'Done!'

const limited = before(3, () => console.log('Call'));
limited(); limited(); limited(); limited(); // Logs only 3 times

_.negate — Invert a Predicate

import negate from 'lodash/negate';

const isEven = n => n % 2 === 0;
const isOdd = negate(isEven);

[1, 2, 3, 4].filter(isOdd); // [1, 3]

Common Mistakes

1. Shallow Clone When Deep Clone Is Needed

// ❌ Spread operator is shallow — nested objects are shared
const config = { theme: { colors: { primary: '#000' } } };
const copy = { ...config };
copy.theme.colors.primary = '#fff'; // Mutates config!

// ✅ _.cloneDeep creates independent copies at every level
const copy = cloneDeep(config);
copy.theme.colors.primary = '#fff'; // config is untouched

2. _.assign vs _.merge Confusion

_.assign is shallow — nested objects are replaced, not merged. _.merge recurses into nested objects. If you lose nested properties, you probably used _.assign when you needed _.merge.

3. Forgetting to Cancel Debounce on Component Unmount

In frameworks like React or Vue, always cancel pending debounce calls in cleanup:

useEffect(() => {
  const save = debounce(() => { /* update state */ }, 500);
  return () => save.cancel(); // Cleanup prevents state updates on unmounted components
}, []);

4. Memoize Memory Leaks

_.memoize caches results forever. For long-running apps (like a dashboard or antivirus tool), periodically clear the cache or use an LRU implementation.

5. Using _.get When Optional Chaining Suffices

Modern JavaScript has optional chaining (?.) and nullish coalescing (??). Use native where possible, but _.get still wins for dynamic paths (computed strings like 'users.' + id + '.profile') and array-path notation.

Practice Questions

1. What does _.clone do that _.cloneDeep does not?

Answer: Nothing additional — _.clone does less. It only copies top-level properties (shallow). _.cloneDeep recursively copies all nested levels.

2. You have { a: 1, nested: { b: 2 } } and you call _.assign({}, obj, { nested: { c: 3 } }). What happens to nested.b?

Answer: nested.b is lost. _.assign is shallow — it replaces the entire nested object with { c: 3 }. Use _.merge to preserve nested.b.

3. What is the difference between debounce and throttle?

Answer: Debounce runs after activity pauses (search-as-you-type). Throttle runs at regular intervals during activity (scroll tracking).

4. How does _.memoize determine the cache key by default?

Answer: It uses the first argument as the cache key. For multi-argument functions, provide a custom resolver function.

Challenge

You’re building a settings panel for Durga Antivirus Pro. Create a config manager that: deep-merges a user’s custom settings ({ scan: { deep: true, folders: ['/home'] } }) with the default config ({ scan: { deep: false, folders: ['/usr'], schedule: 'daily' } }), then safely reads scan.schedule using _.get with a fallback of 'weekly'. Expected merged result: { scan: { deep: true, folders: ['/home'], schedule: 'daily' } }.

FAQ

What is the difference between _.cloneDeep and native structuredClone?

structuredClone is built-in and faster for most cases. However, _.cloneDeep handles more edge cases (Error objects, custom class instances with non-standard prototypes). Use structuredClone for simple data and _.cloneDeep when you need maximum compatibility.

Can I use Lodash debounce with React hooks?

Yes. Wrap the debounced function in useCallback or useRef to maintain the same instance across renders, and call .cancel() in the useEffect cleanup function.

How does _.memoize determine the cache key?

By default, it uses the first argument as the cache key. For composite keys (multiple arguments), pass a custom resolver: _.memoize(fn, (a, b) => \${a}-${b}`)`.

Does _.set create arrays for numeric path segments?

Yes. If a path segment is a valid integer, _.set creates an array at that level instead of an object.

Is _.merge recursive for arrays?

Yes. When merging arrays, _.merge concatenates them rather than replacing. Use _.assign if you want array replacement.

Try It Yourself

Explore deep cloning, nested access, debounce visualization, and memoization performance in this interactive sandbox:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Lodash Object Explorer</title>
  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
    .container { max-width: 1200px; margin: 0 auto; }
    h1 { font-size: 1.75rem; margin-bottom: 0.25rem; }
    .subtitle { color: #94a3b8; margin-bottom: 1.5rem; }
    .tabs { display: flex; gap: 0.25rem; margin-bottom: 1.5rem; border-bottom: 1px solid #334155; }
    .tab { padding: 0.5rem 1rem; cursor: pointer; font-size: 0.875rem; color: #94a3b8; background: none; border: none; border-bottom: 2px solid transparent; }
    .tab:hover { color: #e2e8f0; }
    .tab.active { color: #38bdf8; border-bottom-color: #38bdf8; }
    .panel { display: none; }
    .panel.active { display: block; }
    .card { background: #1e293b; border-radius: 0.75rem; padding: 1.5rem; border: 1px solid #334155; margin-bottom: 1rem; }
    .card h2 { font-size: 1.05rem; margin-bottom: 0.75rem; color: #38bdf8; }
    .card h3 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; }
    textarea { width: 100%; background: #0f172a; color: #e2e8f0; border: 1px solid #334155; border-radius: 0.375rem; padding: 0.625rem; font-family: monospace; font-size: 0.8125rem; min-height: 120px; resize: vertical; }
    .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
    .grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; }
    .btn { background: #38bdf8; color: #0f172a; border: none; padding: 0.4rem 1rem; border-radius: 0.375rem; font-weight: 600; cursor: pointer; font-size: 0.85rem; }
    .btn:hover { background: #7dd3fc; }
    .btn-outline { background: transparent; color: #38bdf8; border: 1px solid #38bdf8; }
    .btn-outline:hover { background: #38bdf8; color: #0f172a; }
    .btn-danger { background: #ef4444; color: #fff; border: none; padding: 0.4rem 0.75rem; border-radius: 0.375rem; font-weight: 600; cursor: pointer; font-size: 0.75rem; }
    .flex { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
    .output-box { background: #0f172a; border: 1px solid #334155; border-radius: 0.375rem; padding: 0.75rem; min-height: 80px; max-height: 250px; overflow: auto; font-family: monospace; font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; margin-top: 0.5rem; }
    .output-box.success { border-color: #22c55e; }
    .output-box.error { border-color: #ef4444; color: #fca5a5; }
    .output-box.warning { border-color: #f59e0b; color: #fde68a; }
    .stat { color: #94a3b8; font-size: 0.8rem; margin-top: 0.5rem; }
    .stat strong { color: #38bdf8; }
    input[type="text"] { background: #0f172a; color: #e2e8f0; border: 1px solid #334155; border-radius: 0.375rem; padding: 0.5rem 0.625rem; font-family: monospace; font-size: 0.875rem; }
    input[type="range"] { width: 100%; accent-color: #38bdf8; }
    .vis-bar { background: #1e293b; border-radius: 0.25rem; height: 30px; margin-top: 0.5rem; overflow: hidden; }
    .vis-fill { height: 100%; background: #38bdf8; transition: width 0.1s; border-radius: 0.25rem; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; color: #0f172a; font-weight: 600; min-width: fit-content; padding: 0 0.5rem; }
    table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
    th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #334155; }
    th { color: #94a3b8; font-weight: 600; }
    td { font-family: monospace; font-size: 0.8rem; }
    @media (max-width: 768px) { .grid-2, .grid-3 { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
<div class="container">
  <h1>Object Explorer</h1>
  <p class="subtitle">Deep clone, merge, deep access, debounce, and memoize interactive demos</p>

  <div class="tabs">
    <button class="tab active" data-tab="clone" onclick="switchTab('clone', this)">Clone & Merge</button>
    <button class="tab" data-tab="access" onclick="switchTab('access', this)">Deep Access</button>
    <button class="tab" data-tab="debounce" onclick="switchTab('debounce', this)">Debounce Viz</button>
    <button class="tab" data-tab="memoize" onclick="switchTab('memoize', this)">Memoize Test</button>
  </div>

  <div id="panel-clone" class="panel active">
    <div class="grid-2">
      <div class="card">
        <h2>Original Object</h2>
        <textarea id="cloneInput">{
  "name": "Alice",
  "age": 25,
  "address": {
    "city": "New York",
    "zip": "10001",
    "coordinates": { "lat": 40.7128, "lng": -74.006 }
  },
  "hobbies": ["reading", "cycling"]
}</textarea>
        <div class="flex" style="margin-top:0.5rem;">
          <button class="btn" onclick="runCloneDeep()">Clone Deep</button>
          <button class="btn btn-outline" onclick="runMergeDemo()">Merge Demo</button>
          <button class="btn btn-outline" onclick="runSpreadDemo()">Spread (Shallow)</button>
        </div>
      </div>
      <div class="card">
        <h2>Result</h2>
        <div class="output-box" id="cloneOutput">Click a button to run</div>
        <div class="flex" style="margin-top:0.5rem;">
          <button class="btn btn-danger btn-sm" onclick="mutateResult()">Mutate Result</button>
        </div>
        <div class="stat" id="cloneStat">Original untouched: --</div>
      </div>
    </div>
  </div>

  <div id="panel-access" class="panel">
    <div class="grid-2">
      <div class="card">
        <h2>Nested Object</h2>
        <textarea id="accessInput">{
  "user": {
    "profile": {
      "name": "Bob",
      "settings": {
        "theme": "dark",
        "notifications": { "email": true, "sms": false }
      }
    },
    "preferences": null
  }
}</textarea>
        <div style="margin-top:0.75rem;">
          <h3>Path</h3>
          <input type="text" id="accessPath" value="user.profile.settings.theme" style="width:100%;" />
        </div>
        <div class="flex" style="margin-top:0.5rem;">
          <button class="btn" onclick="runGet()">_.get</button>
          <button class="btn btn-outline" onclick="runHas()">_.has</button>
          <button class="btn btn-outline" onclick="runSet()">_.set</button>
        </div>
      </div>
      <div class="card">
        <h2>Result</h2>
        <div class="output-box" id="accessOutput">Enter a path and click a button</div>
        <div class="stat" id="accessStat">--</div>
      </div>
    </div>
  </div>

  <div id="panel-debounce" class="panel">
    <div class="card">
      <h2>Debounce Visualizer</h2>
      <p style="color:#94a3b8;font-size:0.875rem;margin-bottom:1rem;">Click rapidly. The debounced function fires only after you pause.</p>
      <div class="flex">
        <button class="btn" id="clickBtn" onclick="handleClick()">Click Me</button>
        <label style="color:#94a3b8;font-size:0.85rem;">Delay: <strong id="delayLabel">300</strong> ms</label>
        <input type="range" id="delaySlider" min="100" max="2000" value="300" step="50" oninput="updateDelay(this.value)" style="max-width:200px;" />
        <button class="btn btn-outline btn-sm" onclick="resetDebounce()">Reset</button>
      </div>
      <div class="grid-3" style="margin-top:1rem;">
        <div><h3>Clicks</h3><div class="output-box" id="clickCount" style="min-height:40px;font-size:1.5rem;display:flex;align-items:center;justify-content:center;">0</div></div>
        <div><h3>Debounced Fires</h3><div class="output-box" id="debounceCount" style="min-height:40px;font-size:1.5rem;display:flex;align-items:center;justify-content:center;">0</div></div>
        <div><h3>Status</h3><div class="output-box" id="debounceStatus" style="min-height:40px;display:flex;align-items:center;justify-content:center;">Waiting...</div></div>
      </div>
      <div class="vis-bar"><div class="vis-fill" id="debounceBar" style="width:0%;">Idle</div></div>
    </div>
  </div>

  <div id="panel-memoize" class="panel">
    <div class="grid-2">
      <div class="card">
        <h2>Fibonacci Calculator</h2>
        <p style="color:#94a3b8;font-size:0.875rem;margin-bottom:0.75rem;">Compare memoized vs non-memoized recursive Fibonacci.</p>
        <div class="flex">
          <label>n = <strong id="fibN">35</strong></label>
          <input type="range" id="fibSlider" min="10" max="42" value="35" step="1" oninput="document.getElementById('fibN').textContent=this.value" style="max-width:200px;" />
          <button class="btn" onclick="runFibTest()">Run Test</button>
        </div>
      </div>
      <div class="card">
        <h2>Results</h2>
        <div class="output-box" id="fibOutput">Run test to compare</div>
        <table style="margin-top:0.75rem;">
          <tr><th>Method</th><th>Time</th><th>Calls</th></tr>
          <tr><td>Without memoize</td><td id="fibWithoutTime">--</td><td id="fibWithoutCalls">--</td></tr>
          <tr><td>With memoize</td><td id="fibWithTime">--</td><td id="fibWithCalls">--</td></tr>
          <tr><td style="color:#38bdf8;">Speedup</td><td colspan="2" id="fibSpeedup" style="color:#38bdf8;">--</td></tr>
        </table>
      </div>
    </div>
  </div>
</div>

<script>
function switchTab(name, btn) {
  document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
  document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
  btn.classList.add('active');
  document.getElementById('panel-' + name).classList.add('active');
}

let clonedResult = null;

function runCloneDeep() {
  const input = document.getElementById('cloneInput');
  let obj;
  try { obj = JSON.parse(input.value); } catch { setCloneOut('Invalid JSON', 'error'); return; }
  clonedResult = _.cloneDeep(obj);
  setCloneOut(JSON.stringify(clonedResult, null, 2), 'success');
  document.getElementById('cloneStat').innerHTML = 'Original untouched: Yes (deep clone is independent)';
}

function runSpreadDemo() {
  const input = document.getElementById('cloneInput');
  let obj;
  try { obj = JSON.parse(input.value); } catch { setCloneOut('Invalid JSON', 'error'); return; }
  const spread = { ...obj };
  document.getElementById('cloneStat').innerHTML = 'Spread is shallow -- nested objects are shared references';
  setCloneOut(JSON.stringify(spread, null, 2), 'warning');
}

function runMergeDemo() {
  const input = document.getElementById('cloneInput');
  let obj;
  try { obj = JSON.parse(input.value); } catch { setCloneOut('Invalid JSON', 'error'); return; }
  const override = { age: 99, address: { city: 'Los Angeles' } };
  const merged = _.merge({}, obj, override);
  setCloneOut(JSON.stringify(merged, null, 2), 'success');
  document.getElementById('cloneStat').innerHTML = 'age: 99, city: "Los Angeles". Original unchanged.';
}

function mutateResult() {
  if (!clonedResult) { setCloneOut('No cloned result to mutate', 'error'); return; }
  if (clonedResult.address) clonedResult.address.city = 'MUTATED';
  if (clonedResult.hobbies) clonedResult.hobbies.push('hacked');
  setCloneOut(JSON.stringify(clonedResult, null, 2), 'warning');
  document.getElementById('cloneStat').innerHTML = 'Result mutated. Original input is unchanged.';
}

function setCloneOut(text, cls) {
  const el = document.getElementById('cloneOutput');
  el.textContent = text;
  el.className = 'output-box ' + (cls || '');
}

function runGet() {
  const obj = parseAccess(); if (!obj) return;
  const path = document.getElementById('accessPath').value;
  try {
    const result = _.get(obj, path, '__UNDEFINED__');
    const display = result === '__UNDEFINED__' ? 'undefined (path not found)' : JSON.stringify(result, null, 2);
    document.getElementById('accessOutput').textContent = display;
    document.getElementById('accessOutput').className = 'output-box ' + (result === '__UNDEFINED__' ? 'warning' : 'success');
    document.getElementById('accessStat').innerHTML = 'Path: ' + path + ' | Type: ' + typeof result;
  } catch (e) { document.getElementById('accessOutput').textContent = 'Error: ' + e.message; document.getElementById('accessOutput').className = 'output-box error'; }
}

function runHas() {
  const obj = parseAccess(); if (!obj) return;
  const path = document.getElementById('accessPath').value;
  const result = _.has(obj, path);
  document.getElementById('accessOutput').textContent = result ? 'true -- path exists' : 'false -- path not found';
  document.getElementById('accessOutput').className = 'output-box ' + (result ? 'success' : 'warning');
  document.getElementById('accessStat').innerHTML = 'Path: ' + path;
}

function runSet() {
  const obj = parseAccess(); if (!obj) return;
  const path = document.getElementById('accessPath').value;
  _.set(obj, path, 'SET-' + Date.now());
  document.getElementById('accessOutput').textContent = JSON.stringify(obj, null, 2);
  document.getElementById('accessOutput').className = 'output-box success';
  document.getElementById('accessStat').innerHTML = 'Set ' + path;
}

function parseAccess() {
  const val = document.getElementById('accessInput').value;
  try { return JSON.parse(val); }
  catch { document.getElementById('accessOutput').textContent = 'Invalid JSON'; document.getElementById('accessOutput').className = 'output-box error'; return null; }
}

let clickCount = 0; let debounceCount = 0; let debounceFn = null;

function createDebounced(delay) {
  if (debounceFn && debounceFn.cancel) debounceFn.cancel();
  debounceFn = _.debounce(function() {
    debounceCount++;
    document.getElementById('debounceCount').textContent = debounceCount;
    document.getElementById('debounceStatus').textContent = 'Fired!';
    document.getElementById('debounceStatus').className = 'output-box success';
    document.getElementById('debounceBar').textContent = 'FIRED';
    document.getElementById('debounceBar').style.width = '100%';
    document.getElementById('debounceBar').style.background = '#22c55e';
    document.getElementById('debounceBar').style.color = '#0f172a';
    setTimeout(function() { document.getElementById('debounceBar').textContent = 'Idle'; document.getElementById('debounceBar').style.width = '0%'; document.getElementById('debounceBar').style.background = '#38bdf8'; }, 500);
  }, parseInt(delay));
}

function handleClick() {
  clickCount++;
  document.getElementById('clickCount').textContent = clickCount;
  const delay = document.getElementById('delaySlider').value;
  if (!debounceFn) createDebounced(delay);
  document.getElementById('debounceStatus').textContent = 'Debouncing...';
  document.getElementById('debounceStatus').className = 'output-box warning';
  document.getElementById('debounceBar').textContent = 'Debouncing...';
  document.getElementById('debounceBar').style.width = '100%';
  document.getElementById('debounceBar').style.background = '#f59e0b';
  document.getElementById('debounceBar').style.color = '#0f172a';
  debounceFn();
}

function updateDelay(val) { document.getElementById('delayLabel').textContent = val; createDebounced(val); }

function resetDebounce() {
  clickCount = 0; debounceCount = 0;
  if (debounceFn && debounceFn.cancel) debounceFn.cancel();
  document.getElementById('clickCount').textContent = '0'; document.getElementById('debounceCount').textContent = '0';
  document.getElementById('debounceStatus').textContent = 'Waiting...'; document.getElementById('debounceStatus').className = 'output-box';
  document.getElementById('debounceBar').textContent = 'Idle'; document.getElementById('debounceBar').style.width = '0%'; document.getElementById('debounceBar').style.background = '#38bdf8';
}

createDebounced(300);

let fibCache = {};
function fibRaw(n) { fibCounter++; if (n <= 1) return n; return fibRaw(n - 1) + fibRaw(n - 2); }
let fibCounter = 0;

const fibMemo = _.memoize(function(n) { fibMemoCounter++; if (n <= 1) return n; return fibMemo(n - 1) + fibMemo(n - 2); });
let fibMemoCounter = 0;

function runFibTest() {
  const n = parseInt(document.getElementById('fibSlider').value);
  fibCounter = 0; fibMemoCounter = 0; fibMemo.cache.clear();
  const start1 = performance.now(); const result1 = fibRaw(n); const time1 = (performance.now() - start1).toFixed(2);
  const start2 = performance.now(); const result2 = fibMemo(n); const time2 = (performance.now() - start2).toFixed(2);
  document.getElementById('fibOutput').textContent = 'fib(' + n + ') = ' + result1; document.getElementById('fibOutput').className = 'output-box success';
  document.getElementById('fibWithoutTime').textContent = time1 + ' ms'; document.getElementById('fibWithoutCalls').textContent = fibCounter.toLocaleString();
  document.getElementById('fibWithTime').textContent = time2 + ' ms'; document.getElementById('fibWithCalls').textContent = fibMemoCounter.toLocaleString();
  document.getElementById('fibSpeedup').textContent = (time1 / time2).toFixed(1) + 'x faster';
}
</script>
</body>
</html>

What’s Next

StepTopicWhy
https://tutorials.dodatech.com/frontend/libraries/lodash/lodash-utilities/String case conversion, math, type checking, lazy evaluationComplete your Lodash toolkit
https://tutorials.dodatech.com/frontend/libraries/lodash/lodash-collections-arrays/_.map, _.filter, _.reduce, _.groupBy, array transformationsBuild on collection skills
https://tutorials.dodatech.com/frontend/libraries/lodash/lodash-getting-started/Installation, imports, chaining fundamentalsReview prerequisites if needed
React PerformanceApply debounce, throttle, and memoize in React appsReal-world framework usage
Node.js ApplicationsServer-side data processing with LodashBackend integration patterns

What’s Next

Congratulations on completing this Lodash Objects Functions tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro