Logo
Published on

Reference Copying

How Reference Copying Affects Your JavaScript Objects

JavaScript's reference copying behavior is a common source of bugs where modifying one object unexpectedly changes another. Understanding how objects share references versus creating true copies is crucial for preventing state mutations that can cascade through your application, especially in React or Redux environments.

TL;DR

  • Assignment operators create references, not copies: obj2 = obj1
  • Spread operator creates shallow copies: ...obj
  • Nested objects still share references even with spread
  • Critical for React state updates and Redux reducers
const copy = { ...original }

The Shared Reference Problem

You're updating user settings in your application, but changes to one user's profile are mysteriously affecting another user's data. The culprit is reference copying - when you assigned one object to another, you created a reference, not a copy. Both variables point to the same object in memory.

// The problematic reference sharing
const user1 = {
  name: 'Alice',
  settings: { theme: 'light', notifications: true },
}
const user2 = user1 // Reference, not a copy!
user2.name = 'Bob'
user2.settings.theme = 'dark'
console.log('User1 name:', user1.name) // Bob (!!)
console.log('User1 theme:', user1.settings.theme) // dark (!!)
console.log('Same object?', user1 === user2) // true

Modern spread syntax creates a proper shallow copy, ensuring changes to one object don't affect the other:

// The proper shallow copy solution
const user1 = {
  name: 'Alice',
  settings: { theme: 'light', notifications: true },
}
const user2 = { ...user1 } // Shallow copy
user2.name = 'Bob'
console.log('User1 name:', user1.name) // Alice (correct!)
console.log('User2 name:', user2.name) // Bob
console.log('Same object?', user1 === user2) // false
// Warning: nested objects still share references
user2.settings.theme = 'dark'
console.log('User1 theme:', user1.settings.theme) // dark (still shared!)

Best Practises

Use reference copying when:

  • ✅ You intentionally want multiple variables to reference the same object
  • ✅ Working with immutable data structures that never change
  • ✅ Passing objects to functions that shouldn't create copies
  • ✅ Memory optimization is critical and duplication must be avoided

Avoid when:

  • 🚩 Working with React state or Redux store updates
  • 🚩 Modifying objects that other parts of code might be using
  • 🚩 Creating independent copies for different users or sessions
  • 🚩 Building undo/redo functionality that needs historical states

System Design Trade-offs

AspectReference AssignmentSpread Shallow Copy
Memory UsageMinimal - shares memoryHigher - duplicates first level
PerformanceInstant O(1)O(n) for object properties
Mutation SafetyNone - all changes sharedFirst level only
Nested ObjectsAll levels sharedStill share references
Use CasesAliases, performanceState updates, cloning
DebuggingHard - unexpected mutationsEasier - isolated changes

More Code Examples

❌ Reference mutation chaos
// Traditional approach with dangerous reference sharing
function processUserData() {
  const originalUser = {
    id: 1,
    profile: {
      name: 'John Doe',
      email: 'john@example.com',
      preferences: {
        language: 'en',
        timezone: 'UTC',
      },
    },
    permissions: ['read', 'write'],
  }
  // Dangerous: all these share the same reference
  const adminUser = originalUser
  const backupUser = originalUser
  const tempUser = originalUser
  // Modifying "adminUser" affects all references
  adminUser.permissions.push('delete')
  adminUser.profile.name = 'Admin John'
  const origPerms = originalUser.permissions
  console.log('Original permissions:', origPerms)
  console.log('Backup permissions:', backupUser.permissions)
  const hasDelete = tempUser.permissions.includes('delete')
  console.log('All have delete:', hasDelete)
  // Attempting to restore from "backup" fails
  const restored = backupUser
  console.log('Restored name:', restored.profile.name) // Still "Admin John"
  // Array reference issues
  const scores = [85, 92, 78]
  const modifiedScores = scores
  modifiedScores.push(100)
  console.log('Original scores length:', scores.length) // 4, not 3!
  return {
    original: originalUser,
    modified: adminUser,
    areSame: originalUser === adminUser, // true
  }
}
const result = processUserData()
console.log('Objects are identical:', result.areSame)
✅ Spread copy safety
// Modern approach with proper shallow copying
function processUserData() {
  const originalUser = {
    id: 1,
    profile: {
      name: 'John Doe',
      email: 'john@example.com',
      preferences: {
        language: 'en',
        timezone: 'UTC',
      },
    },
    permissions: ['read', 'write'],
  }
  // Safe shallow copies
  const adminUser = { ...originalUser }
  const backupUser = { ...originalUser }
  // Fix nested objects with nested spread
  adminUser.profile = { ...originalUser.profile }
  adminUser.permissions = [...originalUser.permissions]
  // Now modifications are isolated
  adminUser.permissions.push('delete')
  adminUser.profile.name = 'Admin John'
  console.log('Original permissions:', originalUser.permissions)
  console.log('Admin permissions:', adminUser.permissions)
  const unchanged = !originalUser.permissions.includes('delete')
  console.log('Original unchanged:', unchanged)
  // Backup remains intact
  console.log('Backup name:', backupUser.profile.name) // "John Doe"
  // Array spreading for safety
  const scores = [85, 92, 78]
  const modifiedScores = [...scores, 100]
  console.log('Original scores:', scores.length) // Still 3
  console.log('Modified scores:', modifiedScores.length) // 4
  // Object.assign alternative
  const assignCopy = Object.assign({}, originalUser)
  console.log('Assign copy works:', assignCopy !== originalUser)
  return {
    original: originalUser,
    modified: adminUser,
    areSame: originalUser === adminUser, // false
  }
}
const result = processUserData()
console.log('Objects are separate:', !result.areSame)

Technical Trivia

The Facebook messenger bug of 2019: A reference copying bug in Facebook's messenger app caused private messages to appear in wrong conversations. Developers had reused message object references across different chat threads, leading to a critical privacy breach affecting thousands of users before emergency patches were deployed.

Why JavaScript uses references: JavaScript's design prioritizes performance and memory efficiency. Copying large objects by default would cause significant performance penalties. The language assumes developers understand reference semantics, but this assumption has led to countless bugs in production applications.

Modern framework solutions: React's immutability requirements and Redux's reducer patterns emerged partly as responses to reference mutation bugs. Tools like Immer now provide immutable update patterns while maintaining familiar mutation syntax, preventing reference-related state corruption.


Master Reference vs Copy: Implementation Guide

Choose spread operator for shallow copies when updating state in React or Redux, ensuring first-level properties are truly independent. For nested objects, combine multiple spreads or use libraries like Lodash's cloneDeep. Remember that reference assignment is powerful for performance but dangerous for mutable operations - use it only when you explicitly want shared references.