Production JavaScript Patterns
Apply JavaScript knowledge to real applications through immutability, composition, dependency injection, state management, concurrency control, and maintainable architecture.
Immutability and Structural Updates
Immutability means treating existing data as unchanged and producing new values when state changes. JavaScript does not make objects immutable by default, so application code must choose appropriate techniques.
Immutability is particularly valuable in state management because reference identity can efficiently signal that something changed.
const user = {
name: 'Suriya',
preferences: {
theme: 'light'
}
};
const updatedUser = {
...user,
preferences: {
...user.preferences,
theme: 'dark'
}
};
console.log(user.preferences.theme);
console.log(updatedUser.preferences.theme);- Spread syntax creates shallow copies.
- Nested updates require copying each changed level.
- Immutability makes state transitions easier to reason about.
Does object spread perform a deep clone?
Dependency Injection with Plain JavaScript
Dependency injection means providing dependencies to a component rather than having it construct them internally. This improves testability, flexibility, and separation of concerns.
JavaScript's functions and objects make dependency injection straightforward without requiring a framework.
function createUserService(userRepository) {
return {
async getUser(id) {
return userRepository.findById(id);
}
};
}
const service = createUserService({
findById: async id => ({ id, name: 'Suriya' })
});- Dependency injection separates construction from behavior.
- Functions can receive dependencies directly.
- Injected dependencies simplify unit testing.
What is the primary benefit of dependency injection?
Controlling Async Concurrency
Starting thousands of asynchronous operations simultaneously can overwhelm APIs, memory, sockets, or downstream systems. Concurrency limiting restricts how many operations are active at once.
Promise.all is useful for independent work but should not automatically be used for unbounded collections. Production systems often need batching, queues, backpressure, retries, timeouts, and cancellation.
async function processInBatches(items, size, worker) {
const results = [];
for (let i = 0; i < items.length; i += size) {
const batch = items.slice(i, i + size);
results.push(...await Promise.all(batch.map(worker)));
}
return results;
}
const results = await processInBatches(items, 10, processItem);- Unlimited Promise concurrency can overload systems.
- Batching is a simple concurrency-control strategy.
- Production async systems often require backpressure and cancellation.
Why can blindly using Promise.all over thousands of items be problematic?
Pure Functions and Side Effects
A pure function produces the same output for the same inputs and does not cause observable side effects. Pure functions are easier to test, cache, compose, and reason about.
Side effects are not inherently bad; applications need I/O, logging, network calls, DOM changes, and state updates. The important architectural technique is to isolate side effects from deterministic business logic where practical.
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
function saveOrder(order) {
return fetch('/api/orders', {
method: 'POST',
body: JSON.stringify(order)
});
}- Pure functions are deterministic and side-effect free.
- Side effects are necessary but should be controlled.
- Separating calculation from I/O improves testability.
Which function is most likely pure?