DEV Community

Alex Chen
Alex Chen

Posted on

JavaScript Objects: 20 Patterns You Should Know

JavaScript Objects: 20 Patterns You Should Know

Objects are the building blocks of JS. Master these patterns.

1. Destructuring

const user = { name: 'Alex', age: 30, email: 'alex@example.com', role: 'admin' };

// Basic destructuring
const { name, age } = user;

// Rename while destructuring
const { name: userName, email: userEmail } = user;

// Default values
const { role = 'user', avatar = '/default.png' } = user;

// Nested destructuring
const { address: { city, country } } = user;
// city = 'San Francisco', country = 'USA'

// Function parameter destructuring
function greet({ name, age }) {
  return `Hi ${name}, you're ${age}!`;
}
greet(user); // "Hi Alex, you're 30!"

// Array + Object destructuring combined
const users = [{ id: 1, name: 'Alex' }, { id: 2, name: 'Sam' }];
const [{ id: firstId }] = users; // firstId = 1
Enter fullscreen mode Exit fullscreen mode

2. Spread Operator

// Clone objects (shallow)
const clone = { ...user };

// Merge objects
const defaults = { theme: 'dark', lang: 'en', notifications: true };
const settings = { theme: 'light', fontSize: 14 };
const merged = { ...defaults, ...settings };
// { theme: 'light', lang: 'en', notifications: true, fontSize: 14 }

// Pick specific keys
const { name, email } = user;
const picked = { name, email };

// Omit specific keys
const { password, ...safeUser } = user; // safeUser has everything except password

// Conditional spread
const config = {
  ...(isDev && { debugMode: true }),
  ...(apiKey && { apiKey }),
};
Enter fullscreen mode Exit fullscreen mode

3. Dynamic Keys

const key = 'email';
const value = 'alex@example.com';

// Dynamic key in object literal
const obj = { [key]: value }; // { email: 'alex@example.com' }

// Computed property names
const prefix = 'user';
const userObj = {
  [`${prefix}_id`]: 123,
  [`${prefix}_name`]: 'Alex',
};

// Dynamic access
obj[key]; // 'alex@example.com'
obj['user_name']; // 'Alex'
Enter fullscreen mode Exit fullscreen mode

4. Optional Chaining

const data = {
  user: {
    profile: {
      address: {
        city: 'San Francisco',
      },
    },
  },
};

// Without optional chaining:
data.user.profile.address.city; // Error if any level is undefined!

// With optional chaining:
data?.user?.profile?.address?.city; // "San Francisco" or undefined

// With default fallback:
data?.user?.profile?.address?.city ?? 'Unknown'; // "San Francisco" or "Unknown"

// Method calls:
user.getProfile?.(); // Only calls if method exists
Enter fullscreen mode Exit fullscreen mode

5. Nullish Coalescing

// || vs ?? — big difference!
const value = 0;
value || 'default';   // 'default' (0 is falsy!)
value ?? 'default';   // 0 (only null/undefined triggers default)

const count = '';
count || 'N/A';      // 'N/A' (empty string is falsy!)
count ?? 'N/A';      // '' (empty string is NOT nullish)

// Practical use:
function getConfig(key) {
  return process.env[key] ?? getDefault(key);
}
Enter fullscreen mode Exit fullscreen mode

6. Object.freeze and Immutability

const config = { debug: false, apiVersion: 'v1' };

Object.freeze(config); // Prevents ANY modifications
config.debug = true;   // Silently fails (strict mode throws)
config.newKey = 'hi';  // Silently fails

// Deep freeze utility:
function deepFreeze(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;

  Object.keys(obj).forEach(key => {
    deepFreeze(obj[key]);
  });

  return Object.freeze(obj);
}

const frozenConfig = deepFreeze({
  db: { host: 'localhost', port: 5432 },
  cache: { ttl: 3600 },
});
Enter fullscreen mode Exit fullscreen mode

7. Object Methods You Forgot Exist

const user = { name: 'Alex', age: 30, role: 'admin' };

// Get all keys
Object.keys(user);     // ['name', 'age', 'role']

// Get all values
Object.values(user);   // ['Alex', 30, 'admin']

// Get entries (array of [key, value] pairs)
Object.entries(user);  // [['name','Alex'], ['age',30], ['role','admin']]

// From entries back to object
Object.fromEntries([['x', 1], ['y', 2]]); // { x: 1, y: 2 }

// Check for own properties
user.hasOwnProperty('name');    // true
'name' in user;                  // true (includes inherited)

// Get all own property descriptors
Object.getOwnPropertyDescriptor(user, 'name');
// { value: 'Alex', writable: true, enumerable: true, configurable: true }

// Compare objects (same reference?)
const a = { x: 1 };
const b = { x: 1 };
a === b;              // false (different references!)

// Shallow compare:
JSON.stringify(a) === JSON.stringify(b); // true (but has edge cases)
Enter fullscreen mode Exit fullscreen mode

8. Map vs Object — When to Use Which

// Use Object when:
// - Simple key-value store with known keys
// - Need JSON serialization
// - Keys are strings/symbols
const config = { host: 'localhost', port: 3000 };

// Use Map when:
// - Keys can be any type (including objects!)
// - Need to preserve insertion order
// - Frequent add/remove of key-value pairs
// - Need to know the size
const cache = new Map();
cache.set('key1', 'value1');
cache.set({ id: 1 }, 'object as key!');
cache.size;          // 2
cache.has('key1');   // true
cache.delete('key1');
cache.clear();

// WeakMap (keys are garbage collected when no other reference exists)
const weakCache = new Map();
// Great for metadata on DOM elements without preventing GC
Enter fullscreen mode Exit fullscreen mode

9. Proxy — Intercept Object Operations

const handler = {
  get(target, prop) {
    console.log(`Reading ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true; // Must return true for success
  },
  deleteProperty(target, prop) {
    console.log(`Deleting ${prop}`);
    delete target[prop];
    return true;
  },
};

const proxyUser = new Proxy(user, handler);

proxyUser.name;        // Logs "Reading name", returns "Alex"
proxyUser.age = 31;    // Logs "Setting age to 31"
delete proxyUser.role; // Logs "Deleting role"

// Practical: Validation proxy
const validator = {
  set(obj, prop, value) {
    const schema = { age: 'number', name: 'string', email: 'string' };
    if (schema[prop] && typeof value !== schema[prop]) {
      throw new TypeError(`${prop} must be ${schema[prop]}`);
    }
    obj[prop] = value;
    return true;
  },
};

const validatedUser = new Proxy({}, validator);
validatedUser.age = 'thirty'; // TypeError!
validatedUser.age = 30;       // OK
Enter fullscreen mode Exit fullscreen mode

10. Practical Patterns

Default config merger

function createConfig(userOptions) {
  const defaults = {
    timeout: 5000,
    retries: 3,
    debug: false,
    headers: {},
  };
  return { ...defaults, ...userOptions };
}
Enter fullscreen mode Exit fullscreen mode

Group by key

const users = [
  { name: 'Alex', dept: 'Engineering' },
  { name: 'Sam', dept: 'Marketing' },
  { name: 'Charlie', dept: 'Engineering' },
];

const grouped = users.reduce((acc, u) => {
  (acc[u.dept] ??= []).push(u.name);
  return acc;
}, {});
// { Engineering: ['Alex', 'Charlie'], Marketing: ['Sam'] }
Enter fullscreen mode Exit fullscreen mode

Transform object keys

const snakeCaseKeys = {
  user_id: 123,
  user_name: 'Alex',
  created_at: '2026-01-15',
};

const camelCase = Object.fromEntries(
  Object.entries(snakeCaseKeys).map(([k, v]) => [
    k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
    v,
  ])
);
// { userId: 123, userName: 'Alex', createdAt: '2026-01-15' }
Enter fullscreen mode Exit fullscreen mode

Deep merge

function isObject(val) {
  return val && typeof val === 'object' && !Array.isArray(val);

function deepMerge(target, ...sources) {
  for (const source of sources) {
    for (const [key, value] of Object.entries(source)) {
      if (isObject(value) && isObject(target[key])) {
        deepMerge(target[key], value);
      } else {
        target[key] = value;
      }
    }
  }
  return target;
}
Enter fullscreen mode Exit fullscreen mode

What's your favorite object pattern? Share it below!

Follow @armorbreak for more JavaScript content.

Top comments (0)