Introduction
Getting Started
- QuickStart
Patterns
- Languages
- Security
- Performance
- CPU-Intensive Operations
- Memory Leaks
- Inefficient Algorithms
- Database Performance
- Network Bottlenecks
- Resource Contention
- Inefficient Data Structures
- Excessive Object Creation
- Synchronization Issues
- I/O Bottlenecks
- String Manipulation
- Inefficient Loops
- Lazy Loading Issues
- Caching Problems
- UI Rendering Bottlenecks
- Serialization Overhead
- Logging overhead
- Reflection misuse
- Thread pool issues
- Garbage collection issues
Integrations
- Code Repositories
- Team Messengers
- Ticketing
Enterprise
Excessive Object Creation
Anti-patterns related to unnecessary object creation that can lead to performance issues.
Creating objects in programming languages with automatic memory management (like Java, JavaScript, Python, etc.) has a cost that is often overlooked. Each object allocation requires memory, initialization time, and eventually triggers garbage collection when the object is no longer needed.
Excessive object creation can lead to:
- Increased memory usage
- More frequent garbage collection pauses
- CPU overhead for object initialization
- Cache pollution
- Reduced application throughput
This guide covers common anti-patterns related to unnecessary object creation, along with best practices for minimizing object allocation overhead across different programming languages and application types.
// Anti-pattern: Creating objects in tight loops
public double calculateAverages(int[] values) {
double sum = 0;
for (int i = 0; i < values.length; i++) {
// Creates a new Double object in each iteration
Double value = Double.valueOf(values[i]);
sum += value;
}
return sum / values.length;
}
// Better approach: Avoiding unnecessary object creation
public double calculateAveragesEfficiently(int[] values) {
double sum = 0;
for (int i = 0; i < values.length; i++) {
// Works directly with primitives, no object creation
sum += values[i];
}
return sum / values.length;
}
// Anti-pattern: Creating objects in tight loops
function processDataInefficiently(data) {
let result = 0;
for (let i = 0; i < 1000000; i++) {
// Creates a new object in each iteration
const wrapper = { value: data[i % data.length] };
result += wrapper.value;
}
return result;
}
// Better approach: Avoiding unnecessary object creation
function processDataEfficiently(data) {
let result = 0;
for (let i = 0; i < 1000000; i++) {
// Works directly with the value, no object creation
result += data[i % data.length];
}
return result;
}
Creating objects within tight loops, especially in performance-critical code, can lead to significant overhead due to memory allocation, initialization, and increased garbage collection pressure.
To avoid unnecessary object creation in loops:
- Use primitive types instead of wrapper classes when possible
- Reuse objects instead of creating new ones in each iteration
- Move object creation outside of loops when the same object can be reused
- Consider object pooling for expensive-to-create objects
- Use value types or structs in languages that support them
- Be aware of hidden object creation (e.g., autoboxing, implicit conversions)
- Use specialized libraries that minimize object creation
- Consider using arrays of primitives instead of collections of objects
- Profile your application to identify hotspots of excessive object creation
// Anti-pattern: Inefficient string manipulation
public String buildMessage(List<String> parts) {
String result = "";
for (String part : parts) {
// Creates a new String object in each iteration
result = result + part;
}
return result;
}
// Better approach: Using StringBuilder
public String buildMessageEfficiently(List<String> parts) {
StringBuilder builder = new StringBuilder();
for (String part : parts) {
// Appends to existing buffer, no new String objects
builder.append(part);
}
return builder.toString();
}
// Anti-pattern: Inefficient string manipulation
function buildMessageInefficiently(parts) {
let result = '';
for (let i = 0; i < parts.length; i++) {
// Creates a new string in each iteration
result = result + parts[i];
}
return result;
}
// Better approach: Using array join
function buildMessageEfficiently(parts) {
// Creates strings only once at the end
return parts.join('');
}
Inefficient string manipulation, particularly concatenation in loops, leads to excessive object creation due to the immutable nature of strings in most programming languages.
To optimize string manipulation:
- Use StringBuilder/StringBuffer in Java
- Use array.join() in JavaScript
- Use ”.join(list) in Python
- Preallocate capacity when the final size is known or can be estimated
- Consider using string interpolation for simple cases
- Use specialized string manipulation libraries for complex operations
- Be aware of hidden string creation in your code
- Minimize unnecessary conversions to and from strings
- Consider using char arrays for low-level string manipulation
- Reuse string buffers when possible
// Anti-pattern: Creating unnecessary intermediate objects in streams
public long countEvenNumbers(List<Integer> numbers) {
return numbers.stream()
.filter(n -> n % 2 == 0) // Creates boxed Integer objects
.count();
}
// Better approach: Using primitive streams
public long countEvenNumbersEfficiently(List<Integer> numbers) {
return numbers.stream()
.mapToInt(Integer::intValue) // Converts to primitive int stream
.filter(n -> n % 2 == 0) // Works with primitives
.count();
}
// Anti-pattern: Creating unnecessary objects in array operations
function processArrayInefficiently(numbers) {
return numbers
.map(n => ({ value: n })) // Creates object for each number
.filter(obj => obj.value % 2 === 0) // Filters based on object property
.map(obj => obj.value) // Extracts value back
.reduce((sum, n) => sum + n, 0); // Sums the values
}
// Better approach: Minimizing intermediate objects
function processArrayEfficiently(numbers) {
return numbers
.filter(n => n % 2 === 0) // Filters directly on values
.reduce((sum, n) => sum + n, 0); // Sums the values
}
Stream and collection processing operations often create unnecessary intermediate objects, especially when using boxed types or when chaining multiple transformations that create temporary objects.
To optimize stream and collection processing:
- Use primitive streams (IntStream, LongStream, DoubleStream) in Java
- Minimize unnecessary mapping operations that create objects
- Use specialized stream operations (sum, average, etc.) for numeric operations
- Consider terminal operations that don’t create intermediate collections
- Use collectors that minimize object creation
- Be aware of hidden boxing/unboxing in stream operations
- Consider using specialized libraries for data processing
- Use stream operations that allow for short-circuiting when possible
- Profile stream operations to identify excessive object creation
- Consider traditional loops for simple operations on small collections
// Anti-pattern: Unnecessary defensive copies
public class DataProcessor {
private final List<String> data;
public DataProcessor(List<String> inputData) {
// Always making a defensive copy
this.data = new ArrayList<>(inputData);
}
public List<String> getData() {
// Always returning a defensive copy
return new ArrayList<>(data);
}
// Processing methods...
}
// Better approach: Selective defensive copies
public class OptimizedDataProcessor {
private final List<String> data;
public OptimizedDataProcessor(List<String> inputData) {
// Make defensive copy only if needed
this.data = Collections.unmodifiableList(new ArrayList<>(inputData));
}
public List<String> getData() {
// Return unmodifiable view instead of copy
return Collections.unmodifiableList(data);
}
// Processing methods...
}
// Anti-pattern: Unnecessary defensive copies
class DataProcessor {
constructor(inputData) {
// Always making a defensive copy
this.data = [...inputData];
}
getData() {
// Always returning a defensive copy
return [...this.data];
}
// Processing methods...
}
// Better approach: Selective defensive copies
class OptimizedDataProcessor {
constructor(inputData) {
// Make defensive copy only if needed
this.data = [...inputData];
// Freeze the array to prevent modifications (not deep)
Object.freeze(this.data);
}
getData() {
// Return original frozen array instead of copy
return this.data;
}
// Processing methods...
}
Making defensive copies of collections or objects is a common practice to ensure encapsulation and immutability, but excessive or unnecessary defensive copying can lead to significant object creation overhead.
To optimize defensive copying:
- Use unmodifiable views instead of copies when possible
- Consider immutable collections or objects
- Make defensive copies only when necessary (e.g., when the source is untrusted)
- Document when methods return references vs. copies
- Use copy-on-write collections for frequently read, rarely written data
- Consider using specialized immutable collection libraries
- Be aware of the performance implications of deep vs. shallow copying
- Use lazy copying or copy-on-write semantics when appropriate
- Consider using value types or records (in languages that support them)
- Evaluate the actual threat model before making defensive copies
// Anti-pattern: Excessive autoboxing
public Integer sumValues(List<Integer> values) {
Integer sum = 0; // Boxed integer
for (Integer value : values) {
// Unboxing value, adding to unboxed sum, then boxing result
sum = sum + value;
}
return sum;
}
// Better approach: Avoiding autoboxing
public int sumValuesEfficiently(List<Integer> values) {
int sum = 0; // Primitive int
for (Integer value : values) {
// Only unboxing value, no boxing of result
sum += value;
}
return sum; // Only boxes once at return if needed
}
// JavaScript doesn't have explicit autoboxing like Java,
// but there are similar issues with type conversions
// Anti-pattern: Inefficient type handling
function processValuesInefficiently(values) {
let result = 0;
for (let i = 0; i < values.length; i++) {
// Implicit conversion between string and number
result += '' + values[i] + '';
}
return Number(result);
}
// Better approach: Proper type handling
function processValuesEfficiently(values) {
let result = 0;
for (let i = 0; i < values.length; i++) {
// Proper numeric addition
result += Number(values[i]);
}
return result;
}
Autoboxing (automatic conversion between primitive types and their wrapper classes) can lead to excessive object creation, especially in loops or other performance-critical code paths.
To minimize autoboxing overhead:
- Use primitive types instead of wrapper classes when possible
- Be aware of implicit autoboxing in arithmetic operations
- Use primitive specialized collections and streams
- Explicitly unbox values when performing multiple operations
- Cache frequently used boxed values
- Be careful with generic methods that force autoboxing
- Consider specialized libraries for primitive collections
- Profile your code to identify hotspots of excessive autoboxing
- In JavaScript, be consistent with numeric types
- Minimize unnecessary type conversions
// Anti-pattern: Inefficient date/time handling
public boolean isWithinBusinessHours(List<Order> orders) {
for (Order order : orders) {
// Creates new Date objects for each comparison
Date orderTime = order.getTimestamp();
Date businessStart = new SimpleDateFormat("HH:mm").parse("09:00");
Date businessEnd = new SimpleDateFormat("HH:mm").parse("17:00");
if (orderTime.before(businessStart) || orderTime.after(businessEnd)) {
return false;
}
}
return true;
}
// Better approach: Reusing date/time objects
public boolean isWithinBusinessHoursEfficiently(List<Order> orders) {
// Create formatter and reference times once
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm");
Date businessStart = formatter.parse("09:00");
Date businessEnd = formatter.parse("17:00");
for (Order order : orders) {
Date orderTime = order.getTimestamp();
if (orderTime.before(businessStart) || orderTime.after(businessEnd)) {
return false;
}
}
return true;
}
// Even better: Using modern Java time API
public boolean isWithinBusinessHoursModern(List<Order> orders) {
// Define business hours once
LocalTime businessStart = LocalTime.of(9, 0);
LocalTime businessEnd = LocalTime.of(17, 0);
for (Order order : orders) {
// Extract just the time component
LocalTime orderTime = order.getTimestamp().toInstant()
.atZone(ZoneId.systemDefault())
.toLocalTime();
if (orderTime.isBefore(businessStart) || orderTime.isAfter(businessEnd)) {
return false;
}
}
return true;
}
// Anti-pattern: Inefficient date/time handling
function isWithinBusinessHoursInefficiently(orders) {
for (const order of orders) {
// Creates new Date objects for each comparison
const orderTime = new Date(order.timestamp);
const businessStart = new Date(orderTime);
const businessEnd = new Date(orderTime);
// Set hours for business start/end
businessStart.setHours(9, 0, 0, 0);
businessEnd.setHours(17, 0, 0, 0);
if (orderTime < businessStart || orderTime > businessEnd) {
return false;
}
}
return true;
}
// Better approach: Reusing date/time logic
function isWithinBusinessHoursEfficiently(orders) {
// Extract just the hour and minute for comparison
function getTimeValue(date) {
return date.getHours() * 60 + date.getMinutes();
}
// Define business hours once as minute values
const businessStartMinutes = 9 * 60; // 9:00 AM
const businessEndMinutes = 17 * 60; // 5:00 PM
for (const order of orders) {
const orderTime = new Date(order.timestamp);
const orderTimeMinutes = getTimeValue(orderTime);
if (orderTimeMinutes < businessStartMinutes ||
orderTimeMinutes > businessEndMinutes) {
return false;
}
}
return true;
}
Date and time objects are often expensive to create and parse, especially when done repeatedly in loops or when using older APIs that create multiple intermediate objects.
To optimize date/time handling:
- Create date formatters and parsers once and reuse them
- Use modern date/time APIs (java.time in Java, Temporal API in JavaScript)
- Extract only the components you need for comparison
- Cache frequently used date/time values
- Consider using epoch time or other numeric representations for simple comparisons
- Be aware of the thread-safety of date/time objects and formatters
- Use specialized libraries for complex date/time operations
- Minimize string parsing and formatting of dates
- Consider using immutable date/time objects
- Be mindful of timezone handling and conversions
// Anti-pattern: Recreating regex patterns repeatedly
public boolean validateEmailsInefficiently(List<String> emails) {
for (String email : emails) {
// Compiles the pattern for each email
Pattern pattern = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
Matcher matcher = pattern.matcher(email);
if (!matcher.matches()) {
return false;
}
}
return true;
}
// Better approach: Compile pattern once
public boolean validateEmailsEfficiently(List<String> emails) {
// Compile the pattern once
Pattern pattern = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
for (String email : emails) {
// Reuse the compiled pattern
Matcher matcher = pattern.matcher(email);
if (!matcher.matches()) {
return false;
}
}
return true;
}
// Even better: Define as static constant
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public boolean validateEmailsOptimally(List<String> emails) {
for (String email : emails) {
// Use the static pattern
Matcher matcher = EMAIL_PATTERN.matcher(email);
if (!matcher.matches()) {
return false;
}
}
return true;
}
// Anti-pattern: Recreating regex patterns repeatedly
function validateEmailsInefficiently(emails) {
for (const email of emails) {
// Creates a new RegExp object for each email
const pattern = new RegExp('^[A-Za-z0-9+_.-]+@(.+)$');
if (!pattern.test(email)) {
return false;
}
}
return true;
}
// Better approach: Define regex once
function validateEmailsEfficiently(emails) {
// Define the pattern once
const pattern = /^[A-Za-z0-9+_.-]+@(.+)$/;
for (const email of emails) {
// Reuse the same pattern
if (!pattern.test(email)) {
return false;
}
}
return true;
}
Regular expressions are powerful but can be expensive to compile and execute. Creating new regex pattern objects repeatedly, especially in loops or frequently called methods, leads to unnecessary object creation and performance overhead.
To optimize regular expression usage:
- Compile regex patterns once and reuse them
- Define frequently used patterns as static constants
- Consider using simpler string operations when regex is overkill
- Be aware of regex engine limitations and backtracking issues
- Test regex performance with realistic inputs
- Use non-capturing groups when capture isn’t needed
- Optimize regex patterns for efficiency
- Consider using specialized regex libraries for complex patterns
- Cache regex results for repeated operations on the same input
- In JavaScript, use literal notation (/pattern/) instead of constructor (new RegExp())
// Anti-pattern: Inefficient collection conversions
public void processDataInefficiently(Map<String, User> userMap) {
// Convert map to list of entries
List<Map.Entry<String, User>> entries = new ArrayList<>(userMap.entrySet());
// Sort entries by user age
Collections.sort(entries, (e1, e2) ->
e1.getValue().getAge() - e2.getValue().getAge());
// Convert back to map for further processing
Map<String, User> sortedMap = new LinkedHashMap<>();
for (Map.Entry<String, User> entry : entries) {
sortedMap.put(entry.getKey(), entry.getValue());
}
// Convert to list of users for final processing
List<User> users = new ArrayList<>(sortedMap.values());
processUsers(users);
}
// Better approach: Minimize conversions
public void processDataEfficiently(Map<String, User> userMap) {
// Extract and sort users directly
List<User> users = new ArrayList<>(userMap.values());
Collections.sort(users, Comparator.comparingInt(User::getAge));
// Process the sorted list directly
processUsers(users);
}
// Anti-pattern: Inefficient collection conversions
function processDataInefficiently(userMap) {
// Convert map to array of entries
const entries = Array.from(userMap.entries());
// Sort entries by user age
entries.sort((e1, e2) => e1[1].age - e2[1].age);
// Convert back to map for further processing
const sortedMap = new Map(entries);
// Convert to array of users for final processing
const users = Array.from(sortedMap.values());
processUsers(users);
}
// Better approach: Minimize conversions
function processDataEfficiently(userMap) {
// Extract and sort users directly
const users = Array.from(userMap.values());
users.sort((u1, u2) => u1.age - u2.age);
// Process the sorted array directly
processUsers(users);
}
Converting between different collection types (lists, maps, sets, arrays) often involves creating new collection objects and copying elements, which can be expensive, especially for large collections or when done frequently.
To optimize collection conversions:
- Minimize unnecessary conversions between collection types
- Choose the right collection type for your primary operations
- Use views or wrappers instead of copying when possible
- Consider the entire processing pipeline to avoid intermediate conversions
- Use collection APIs that minimize object creation
- Be aware of the performance characteristics of different conversion methods
- Consider specialized collection libraries for complex operations
- Use streams or functional approaches that can optimize operations
- Cache conversion results when the same conversion is needed multiple times
- Consider using custom collection implementations for specific needs
// Anti-pattern: Inefficient serialization
public byte[] serializeInefficiently(List<DataRecord> records) {
// Creates a new ByteArrayOutputStream for each call
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
// Serialize each record individually
for (DataRecord record : records) {
// Reset and recreate streams for each record
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(record);
byte[] recordBytes = baos.toByteArray();
storeRecord(recordBytes);
}
return baos.toByteArray();
}
// Better approach: Efficient serialization
public byte[] serializeEfficiently(List<DataRecord> records) {
// Create streams once
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
// Write the entire list at once
oos.writeObject(records);
return baos.toByteArray();
}
// Anti-pattern: Inefficient serialization
function serializeInefficiently(records) {
const serializedRecords = [];
// Serialize each record individually
for (const record of records) {
// Creates a new string for each record
const serialized = JSON.stringify(record);
serializedRecords.push(serialized);
}
// Join all serialized records
return serializedRecords.join(',');
}
// Better approach: Efficient serialization
function serializeEfficiently(records) {
// Serialize the entire array at once
return JSON.stringify(records);
}
Inefficient object serialization, such as serializing objects individually or recreating serialization infrastructure for each operation, can lead to excessive object creation and poor performance.
To optimize serialization:
- Serialize collections or batches of objects together when possible
- Reuse serialization infrastructure (streams, buffers, etc.)
- Consider more efficient serialization formats (Protocol Buffers, MessagePack, etc.)
- Implement custom serialization for complex objects
- Use binary serialization instead of text-based formats when appropriate
- Consider the trade-offs between serialization speed and size
- Cache serialization results for frequently serialized objects
- Use specialized serialization libraries optimized for performance
- Be mindful of versioning and compatibility
- Profile serialization performance with realistic data volumes
Object Creation Best Practices Checklist:
1. Reuse Objects
- Pool expensive objects (database connections, threads, etc.)
- Reuse temporary objects in loops and hot paths
- Consider object caching for frequently used objects
- Use static factory methods that can return cached instances
- Implement flyweight pattern for objects with shared state
2. Minimize Allocations
- Use primitives instead of wrapper classes when possible
- Avoid unnecessary autoboxing and unboxing
- Use specialized primitive collections when appropriate
- Preallocate collections with known or estimated capacity
- Avoid creating objects in tight loops or hot paths
3. Optimize String Handling
- Use StringBuilder/StringBuffer for string concatenation
- Minimize string parsing and formatting operations
- Reuse string patterns and formatters
- Consider interning strings for frequently used values
- Use specialized string manipulation libraries when needed
4. Efficient Collection Usage
- Choose the right collection type for your use case
- Minimize conversions between collection types
- Use views or wrappers instead of copying collections
- Consider specialized collection implementations
- Be mindful of collection growth and resizing
5. Optimize for Garbage Collection
- Be aware of object lifecycles and memory pressure
- Consider object sizes and memory footprint
- Minimize short-lived object creation in hot paths
- Profile and monitor garbage collection performance
- Consider memory-efficient data structures
Minimizing unnecessary object creation is a key aspect of optimizing application performance, especially in garbage-collected languages. By following best practices for object creation and reuse, you can reduce memory pressure, minimize garbage collection pauses, and improve overall application throughput.
Key principles for efficient object creation:
- Create objects only when necessary
- Reuse objects when possible
- Use object pooling for expensive resources
- Be mindful of hidden object creation
- Choose appropriate data structures
- Optimize for your language’s memory model
- Consider the entire object lifecycle
- Profile and measure object creation hotspots
- Balance object reuse with code readability
- Be aware of thread-safety implications when reusing objects