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
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 }),
};
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'
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
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);
}
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 },
});
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)
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
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
10. Practical Patterns
Default config merger
function createConfig(userOptions) {
const defaults = {
timeout: 5000,
retries: 3,
debug: false,
headers: {},
};
return { ...defaults, ...userOptions };
}
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'] }
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' }
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;
}
What's your favorite object pattern? Share it below!
Follow @armorbreak for more JavaScript content.
Top comments (0)