🟑 MediumClosure TrapsπŸ› Debug Challenge

Incorrect mutable default object in parameter

Buggy Code β€” Can you spot the issue?

// Anti-pattern inherited from Python thinking
const DEFAULT_CONFIG = { tags: [] };

function createPost(title, config = DEFAULT_CONFIG) {
  config.tags.push('auto-tagged');
  return { title, ...config };
}

const p1 = createPost('Hello');
const p2 = createPost('World');

console.log(p1.tags);
console.log(p2.tags);

Fixed Code

function createPost(title, config = {}) {
  // Create a new config by merging defaults with provided config
  const finalConfig = {
    tags: [],
    category: 'uncategorized',
    ...config,
    tags: [...(config.tags || [])] // shallow copy of tags array
  };
  finalConfig.tags.push('auto-tagged');
  return { title, ...finalConfig };
}

// Or even simpler with destructuring defaults:
function createPost(title, { tags = [], category = 'uncategorized' } = {}) {
  return { title, tags: [...tags, 'auto-tagged'], category };
}

Bug Explained

Bug: DEFAULT_CONFIG is a shared mutable object. config.tags.push() mutates the shared tags array. p2 inherits all mutations from p1's call.

Explanation: Using a shared mutable object as a default value causes state to accumulate across calls. Use {} as the default and create fresh arrays/objects inside the function using spread.

Key Insight: Never use mutable objects/arrays as default parameter values if you mutate them. The default is evaluated once and shared β€” use {} and create new objects inside.

More Closure Traps Debug Challenges

🟒 Easyvar in loop β€” buttons all say last valueβ†’πŸŸ‘ MediumStale closure in React useEffectβ†’πŸ”΄ HardMemoization closure caching wrong scopeβ†’πŸŸ‘ MediumPrivate variable accidentally exposedβ†’

Practice spotting bugs live β†’

38 debug challenges with AI hints

πŸ› Try Debug Lab