JavaScript Design Patterns: Essential Patterns for Better Code
Master JavaScript design patterns including Singleton, Observer, Factory, Module, Strategy, and more. Learn when and how to apply these patterns effectively.
Design patterns are reusable solutions to common programming problems. They provide proven approaches to structuring code, improving maintainability, and solving recurring design challenges. This guide covers essential JavaScript design patterns with practical examples.
Creational Patterns
Creational patterns deal with object creation mechanisms, providing flexibility in deciding which objects to create and how to create them.
Singleton Pattern
The Singleton pattern ensures a class has only one instance and provides global access to it.
// Classic Singleton Pattern
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = {};
this.timestamp = Date.now();
Singleton.instance = this;
return this;
}
getInstance() {
return this;
}
setData(key, value) {
this.data[key] = value;
}
getData(key) {
return this.data[key];
}
clearData() {
this.data = {};
}
}
// Modern Singleton using Module Pattern
const ConfigManager = (() => {
let instance;
function createInstance() {
return {
config: {},
set(key, value) {
this.config[key] = value;
},
get(key) {
return this.config[key];
},
getAll() {
return { ...this.config };
},
reset() {
this.config = {};
},
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
// Lazy Singleton with Proxy
class LazyLoggerSingleton {
constructor() {
this.logs = [];
this.level = 'info';
}
log(message, level = 'info') {
const timestamp = new Date().toISOString();
this.logs.push({ message, level, timestamp });
if (this.shouldOutput(level)) {
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`);
}
}
shouldOutput(level) {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
return levels[level] >= levels[this.level];
}
setLevel(level) {
this.level = level;
}
getLogs(level) {
return level ? this.logs.filter((log) => log.level === level) : this.logs;
}
clear() {
this.logs = [];
}
}
const Logger = new Proxy(LazyLoggerSingleton, {
construct(target, args) {
if (!target.instance) {
target.instance = new target(...args);
}
return target.instance;
},
});
// Database Connection Singleton
class DatabaseConnection {
constructor(config) {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.config = config;
this.connected = false;
this.queries = [];
DatabaseConnection.instance = this;
return this;
}
async connect() {
if (this.connected) {
return Promise.resolve();
}
// Simulate database connection
return new Promise((resolve) => {
setTimeout(() => {
this.connected = true;
console.log('Database connected');
resolve();
}, 1000);
});
}
async query(sql, params = []) {
if (!this.connected) {
await this.connect();
}
const query = { sql, params, timestamp: Date.now() };
this.queries.push(query);
// Simulate query execution
return new Promise((resolve) => {
setTimeout(() => {
resolve({ data: [], query });
}, 100);
});
}
getQueryHistory() {
return [...this.queries];
}
disconnect() {
this.connected = false;
console.log('Database disconnected');
}
}
// Usage examples
const config1 = ConfigManager.getInstance();
const config2 = ConfigManager.getInstance();
console.log(config1 === config2); // true
config1.set('apiUrl', 'https://api.example.com');
console.log(config2.get('apiUrl')); // 'https://api.example.com'
const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true
logger1.log('Application started');
logger2.log('User logged in');
console.log(logger1.getLogs()); // Both logs appear
Factory Pattern
The Factory pattern creates objects without specifying their exact classes, providing an interface for creating families of related objects.
// Simple Factory Pattern
class VehicleFactory {
static createVehicle(type, options = {}) {
switch (type.toLowerCase()) {
case 'car':
return new Car(options);
case 'motorcycle':
return new Motorcycle(options);
case 'truck':
return new Truck(options);
default:
throw new Error(`Vehicle type "${type}" is not supported`);
}
}
static getSupportedTypes() {
return ['car', 'motorcycle', 'truck'];
}
}
class Vehicle {
constructor(options = {}) {
this.make = options.make || 'Unknown';
this.model = options.model || 'Unknown';
this.year = options.year || new Date().getFullYear();
this.color = options.color || 'white';
}
start() {
console.log(`${this.make} ${this.model} started`);
}
stop() {
console.log(`${this.make} ${this.model} stopped`);
}
getInfo() {
return `${this.year} ${this.make} ${this.model} (${this.color})`;
}
}
class Car extends Vehicle {
constructor(options = {}) {
super(options);
this.type = 'car';
this.doors = options.doors || 4;
this.transmission = options.transmission || 'automatic';
}
honk() {
console.log('Beep beep!');
}
}
class Motorcycle extends Vehicle {
constructor(options = {}) {
super(options);
this.type = 'motorcycle';
this.engineSize = options.engineSize || 250;
this.hasHelmet = options.hasHelmet || false;
}
revEngine() {
console.log('Vroom vroom!');
}
}
class Truck extends Vehicle {
constructor(options = {}) {
super(options);
this.type = 'truck';
this.capacity = options.capacity || 1000;
this.axles = options.axles || 2;
}
loadCargo(weight) {
if (weight <= this.capacity) {
console.log(`Loaded ${weight}kg cargo`);
return true;
} else {
console.log(
`Cannot load ${weight}kg - exceeds capacity of ${this.capacity}kg`
);
return false;
}
}
}
// Abstract Factory Pattern
class UIComponentFactory {
static createFactory(theme) {
switch (theme) {
case 'light':
return new LightThemeFactory();
case 'dark':
return new DarkThemeFactory();
case 'material':
return new MaterialThemeFactory();
default:
throw new Error(`Theme "${theme}" is not supported`);
}
}
}
class ThemeFactory {
createButton(text, onClick) {
throw new Error('createButton method must be implemented');
}
createInput(placeholder) {
throw new Error('createInput method must be implemented');
}
createModal(title, content) {
throw new Error('createModal method must be implemented');
}
}
class LightThemeFactory extends ThemeFactory {
createButton(text, onClick) {
return {
type: 'button',
text,
onClick,
styles: {
backgroundColor: '#f0f0f0',
color: '#333',
border: '1px solid #ccc',
borderRadius: '4px',
padding: '8px 16px',
},
render() {
const button = document.createElement('button');
button.textContent = this.text;
button.onclick = this.onClick;
Object.assign(button.style, this.styles);
return button;
},
};
}
createInput(placeholder) {
return {
type: 'input',
placeholder,
styles: {
backgroundColor: '#fff',
color: '#333',
border: '1px solid #ccc',
borderRadius: '4px',
padding: '8px',
},
render() {
const input = document.createElement('input');
input.placeholder = this.placeholder;
Object.assign(input.style, this.styles);
return input;
},
};
}
}
class DarkThemeFactory extends ThemeFactory {
createButton(text, onClick) {
return {
type: 'button',
text,
onClick,
styles: {
backgroundColor: '#333',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
padding: '8px 16px',
},
render() {
const button = document.createElement('button');
button.textContent = this.text;
button.onclick = this.onClick;
Object.assign(button.style, this.styles);
return button;
},
};
}
createInput(placeholder) {
return {
type: 'input',
placeholder,
styles: {
backgroundColor: '#222',
color: '#fff',
border: '1px solid #555',
borderRadius: '4px',
padding: '8px',
},
render() {
const input = document.createElement('input');
input.placeholder = this.placeholder;
Object.assign(input.style, this.styles);
return input;
},
};
}
}
// Plugin Factory Pattern
class PluginFactory {
constructor() {
this.plugins = new Map();
}
register(name, pluginClass) {
this.plugins.set(name, pluginClass);
}
create(name, options = {}) {
const PluginClass = this.plugins.get(name);
if (!PluginClass) {
throw new Error(`Plugin "${name}" is not registered`);
}
return new PluginClass(options);
}
getAvailablePlugins() {
return Array.from(this.plugins.keys());
}
unregister(name) {
return this.plugins.delete(name);
}
}
// Plugin base class
class Plugin {
constructor(options = {}) {
this.name = options.name || 'Unknown Plugin';
this.version = options.version || '1.0.0';
this.enabled = true;
}
initialize() {
console.log(`${this.name} v${this.version} initialized`);
}
execute() {
throw new Error('execute method must be implemented');
}
destroy() {
console.log(`${this.name} destroyed`);
}
}
class LoggerPlugin extends Plugin {
constructor(options = {}) {
super({ name: 'Logger Plugin', ...options });
this.level = options.level || 'info';
}
execute(message, level = this.level) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`);
}
}
class ValidatorPlugin extends Plugin {
constructor(options = {}) {
super({ name: 'Validator Plugin', ...options });
this.rules = options.rules || {};
}
execute(data) {
const errors = [];
for (const [field, value] of Object.entries(data)) {
const rule = this.rules[field];
if (rule && !rule.validate(value)) {
errors.push({
field,
message: rule.message || `${field} is invalid`,
});
}
}
return {
isValid: errors.length === 0,
errors,
};
}
}
// Usage examples
const car = VehicleFactory.createVehicle('car', {
make: 'Toyota',
model: 'Camry',
year: 2023,
doors: 4,
});
const motorcycle = VehicleFactory.createVehicle('motorcycle', {
make: 'Honda',
model: 'CBR600RR',
engineSize: 600,
});
console.log(car.getInfo()); // "2023 Toyota Camry (white)"
car.honk(); // "Beep beep!"
const lightFactory = UIComponentFactory.createFactory('light');
const darkFactory = UIComponentFactory.createFactory('dark');
const lightButton = lightFactory.createButton('Click me', () =>
alert('Light theme!')
);
const darkButton = darkFactory.createButton('Click me', () =>
alert('Dark theme!')
);
// Plugin factory usage
const pluginFactory = new PluginFactory();
pluginFactory.register('logger', LoggerPlugin);
pluginFactory.register('validator', ValidatorPlugin);
const logger = pluginFactory.create('logger', { level: 'debug' });
const validator = pluginFactory.create('validator', {
rules: {
email: {
validate: (value) => /\S+@\S+\.\S+/.test(value),
message: 'Invalid email format',
},
},
});
logger.execute('Application started', 'info');
const result = validator.execute({ email: 'invalid-email' });
console.log(result); // { isValid: false, errors: [...] }
Builder Pattern
The Builder pattern constructs complex objects step by step, allowing you to create different representations of an object using the same construction process.
// Fluent Builder Pattern
class QueryBuilder {
constructor() {
this.reset();
}
reset() {
this.query = {
type: '',
table: '',
fields: [],
conditions: [],
joins: [],
orderBy: [],
groupBy: [],
limit: null,
offset: null,
};
return this;
}
select(fields = ['*']) {
this.query.type = 'SELECT';
this.query.fields = Array.isArray(fields) ? fields : [fields];
return this;
}
from(table) {
this.query.table = table;
return this;
}
where(condition, operator = 'AND') {
this.query.conditions.push({ condition, operator });
return this;
}
orWhere(condition) {
return this.where(condition, 'OR');
}
join(table, on, type = 'INNER') {
this.query.joins.push({ table, on, type });
return this;
}
leftJoin(table, on) {
return this.join(table, on, 'LEFT');
}
rightJoin(table, on) {
return this.join(table, on, 'RIGHT');
}
orderBy(field, direction = 'ASC') {
this.query.orderBy.push({ field, direction });
return this;
}
groupBy(fields) {
this.query.groupBy = Array.isArray(fields) ? fields : [fields];
return this;
}
limit(count) {
this.query.limit = count;
return this;
}
offset(count) {
this.query.offset = count;
return this;
}
build() {
let sql = '';
if (this.query.type === 'SELECT') {
sql += `SELECT ${this.query.fields.join(', ')}`;
sql += ` FROM ${this.query.table}`;
// Add joins
this.query.joins.forEach((join) => {
sql += ` ${join.type} JOIN ${join.table} ON ${join.on}`;
});
// Add conditions
if (this.query.conditions.length > 0) {
sql += ' WHERE ';
sql += this.query.conditions
.map((cond, index) => {
if (index === 0) return cond.condition;
return `${cond.operator} ${cond.condition}`;
})
.join(' ');
}
// Add group by
if (this.query.groupBy.length > 0) {
sql += ` GROUP BY ${this.query.groupBy.join(', ')}`;
}
// Add order by
if (this.query.orderBy.length > 0) {
sql += ' ORDER BY ';
sql += this.query.orderBy
.map((order) => `${order.field} ${order.direction}`)
.join(', ');
}
// Add limit and offset
if (this.query.limit !== null) {
sql += ` LIMIT ${this.query.limit}`;
}
if (this.query.offset !== null) {
sql += ` OFFSET ${this.query.offset}`;
}
}
return sql;
}
execute() {
const sql = this.build();
console.log('Executing SQL:', sql);
// Here you would execute the actual SQL query
return Promise.resolve({ sql, data: [] });
}
clone() {
const builder = new QueryBuilder();
builder.query = JSON.parse(JSON.stringify(this.query));
return builder;
}
}
// Form Builder Pattern
class FormBuilder {
constructor() {
this.form = {
fields: [],
validators: [],
layout: 'vertical',
theme: 'default',
};
}
addField(config) {
const field = {
type: config.type || 'text',
name: config.name,
label: config.label || config.name,
placeholder: config.placeholder || '',
required: config.required || false,
value: config.value || '',
attributes: config.attributes || {},
validators: config.validators || [],
};
this.form.fields.push(field);
return this;
}
addTextField(name, label, options = {}) {
return this.addField({
type: 'text',
name,
label,
...options,
});
}
addEmailField(name, label, options = {}) {
return this.addField({
type: 'email',
name,
label,
validators: [
{
type: 'email',
message: 'Please enter a valid email address',
},
],
...options,
});
}
addPasswordField(name, label, options = {}) {
return this.addField({
type: 'password',
name,
label,
validators: [
{
type: 'minLength',
value: 8,
message: 'Password must be at least 8 characters long',
},
],
...options,
});
}
addSelectField(name, label, options, config = {}) {
return this.addField({
type: 'select',
name,
label,
options,
...config,
});
}
addCheckboxField(name, label, options = {}) {
return this.addField({
type: 'checkbox',
name,
label,
...options,
});
}
addValidator(validator) {
this.form.validators.push(validator);
return this;
}
setLayout(layout) {
this.form.layout = layout;
return this;
}
setTheme(theme) {
this.form.theme = theme;
return this;
}
build() {
const formElement = document.createElement('form');
formElement.className = `form form--${this.form.layout} form--${this.form.theme}`;
this.form.fields.forEach((field) => {
const fieldElement = this.createFieldElement(field);
formElement.appendChild(fieldElement);
});
return {
element: formElement,
config: this.form,
validate: () => this.validate(),
getData: () => this.getData(formElement),
setData: (data) => this.setData(formElement, data),
};
}
createFieldElement(field) {
const wrapper = document.createElement('div');
wrapper.className = `form-field form-field--${field.type}`;
// Create label
if (field.label) {
const label = document.createElement('label');
label.textContent = field.label;
label.setAttribute('for', field.name);
if (field.required) {
label.textContent += ' *';
}
wrapper.appendChild(label);
}
// Create input
let input;
switch (field.type) {
case 'select':
input = document.createElement('select');
if (field.options) {
field.options.forEach((option) => {
const optionElement = document.createElement('option');
optionElement.value = option.value;
optionElement.textContent = option.label;
input.appendChild(optionElement);
});
}
break;
case 'textarea':
input = document.createElement('textarea');
break;
default:
input = document.createElement('input');
input.type = field.type;
}
input.name = field.name;
input.id = field.name;
input.value = field.value;
input.placeholder = field.placeholder;
input.required = field.required;
// Apply additional attributes
Object.entries(field.attributes).forEach(([key, value]) => {
input.setAttribute(key, value);
});
wrapper.appendChild(input);
return wrapper;
}
validate() {
// Implementation for form validation
return { isValid: true, errors: [] };
}
getData(formElement) {
const data = {};
const inputs = formElement.querySelectorAll('input, select, textarea');
inputs.forEach((input) => {
if (input.type === 'checkbox') {
data[input.name] = input.checked;
} else {
data[input.name] = input.value;
}
});
return data;
}
setData(formElement, data) {
Object.entries(data).forEach(([name, value]) => {
const input = formElement.querySelector(`[name="${name}"]`);
if (input) {
if (input.type === 'checkbox') {
input.checked = value;
} else {
input.value = value;
}
}
});
}
}
// Usage examples
const query = new QueryBuilder()
.select(['id', 'name', 'email'])
.from('users')
.leftJoin('profiles', 'users.id = profiles.user_id')
.where('users.active = 1')
.orWhere('users.premium = 1')
.orderBy('users.created_at', 'DESC')
.limit(10)
.build();
console.log(query);
// SELECT id, name, email FROM users LEFT JOIN profiles ON users.id = profiles.user_id WHERE users.active = 1 OR users.premium = 1 ORDER BY users.created_at DESC LIMIT 10
const loginForm = new FormBuilder()
.addEmailField('email', 'Email Address', { required: true })
.addPasswordField('password', 'Password', { required: true })
.addCheckboxField('remember', 'Remember me')
.setLayout('vertical')
.setTheme('material')
.build();
document.body.appendChild(loginForm.element);
Behavioral Patterns
Behavioral patterns focus on communication between objects and the assignment of responsibilities.
Observer Pattern
The Observer pattern defines a one-to-many dependency between objects, so when one object changes state, all dependents are notified automatically.
// Event Emitter Observer Pattern
class EventEmitter {
constructor() {
this.events = new Map();
this.maxListeners = 10;
}
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
const listeners = this.events.get(event);
if (listeners.length >= this.maxListeners) {
console.warn(
`Warning: Possible EventEmitter memory leak detected. ${listeners.length + 1} ${event} listeners added.`
);
}
listeners.push(listener);
return this;
}
once(event, listener) {
const onceWrapper = (...args) => {
listener.apply(this, args);
this.off(event, onceWrapper);
};
return this.on(event, onceWrapper);
}
off(event, listener) {
if (!this.events.has(event)) {
return this;
}
const listeners = this.events.get(event);
const index = listeners.indexOf(listener);
if (index > -1) {
listeners.splice(index, 1);
}
if (listeners.length === 0) {
this.events.delete(event);
}
return this;
}
emit(event, ...args) {
if (!this.events.has(event)) {
return false;
}
const listeners = this.events.get(event).slice(); // Clone to avoid issues with modifications during iteration
listeners.forEach((listener) => {
try {
listener.apply(this, args);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
return true;
}
removeAllListeners(event) {
if (event) {
this.events.delete(event);
} else {
this.events.clear();
}
return this;
}
listenerCount(event) {
return this.events.has(event) ? this.events.get(event).length : 0;
}
eventNames() {
return Array.from(this.events.keys());
}
setMaxListeners(n) {
this.maxListeners = n;
return this;
}
}
// Subject-Observer Pattern
class Subject {
constructor() {
this.observers = [];
this.state = {};
}
subscribe(observer) {
if (typeof observer.update !== 'function') {
throw new Error('Observer must have an update method');
}
this.observers.push(observer);
return {
unsubscribe: () => {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
},
};
}
unsubscribe(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify(data) {
this.observers.forEach((observer) => {
try {
observer.update(data, this);
} catch (error) {
console.error('Error notifying observer:', error);
}
});
}
setState(newState) {
const previousState = { ...this.state };
this.state = { ...this.state, ...newState };
this.notify({
type: 'stateChange',
previousState,
currentState: this.state,
changes: newState,
});
}
getState() {
return { ...this.state };
}
}
// Store with Observer Pattern
class Store extends EventEmitter {
constructor(initialState = {}, reducer = (state, action) => state) {
super();
this.state = initialState;
this.reducer = reducer;
this.middleware = [];
}
getState() {
return { ...this.state };
}
dispatch(action) {
const previousState = this.state;
// Apply middleware
let processedAction = action;
for (const middleware of this.middleware) {
processedAction = middleware(this)(processedAction);
}
// Apply reducer
this.state = this.reducer(this.state, processedAction);
// Notify subscribers
this.emit('stateChange', {
action: processedAction,
previousState,
currentState: this.state,
});
return processedAction;
}
subscribe(listener) {
return this.on('stateChange', listener);
}
addMiddleware(middleware) {
this.middleware.push(middleware);
}
}
// Model-View Observer Pattern
class Model extends EventEmitter {
constructor(data = {}) {
super();
this.data = { ...data };
this.validators = new Map();
this.errors = new Map();
}
get(key) {
return this.data[key];
}
set(key, value) {
const oldValue = this.data[key];
// Validate
if (this.validators.has(key)) {
const validator = this.validators.get(key);
const isValid = validator(value);
if (!isValid) {
this.errors.set(key, `Invalid value for ${key}`);
this.emit('validationError', {
key,
value,
error: this.errors.get(key),
});
return false;
} else {
this.errors.delete(key);
}
}
this.data[key] = value;
this.emit('change', { key, value, oldValue });
this.emit(`change:${key}`, { value, oldValue });
return true;
}
setValidator(key, validator) {
this.validators.set(key, validator);
}
isValid() {
return this.errors.size === 0;
}
getErrors() {
return new Map(this.errors);
}
toJSON() {
return { ...this.data };
}
}
class View {
constructor(model, element) {
this.model = model;
this.element = element;
this.bindings = new Map();
this.setupEventListeners();
}
setupEventListeners() {
this.model.on('change', (data) => {
this.render(data);
});
this.model.on('validationError', (error) => {
this.showError(error);
});
}
render(data) {
// Update view based on model changes
if (this.bindings.has(data.key)) {
const elements = this.bindings.get(data.key);
elements.forEach((element) => {
if (element.type === 'text') {
element.node.textContent = data.value;
} else if (element.type === 'value') {
element.node.value = data.value;
}
});
}
}
bind(key, selector, type = 'text') {
const elements = this.element.querySelectorAll(selector);
if (!this.bindings.has(key)) {
this.bindings.set(key, []);
}
elements.forEach((element) => {
this.bindings.get(key).push({ node: element, type });
// Set initial value
const value = this.model.get(key);
if (value !== undefined) {
if (type === 'text') {
element.textContent = value;
} else if (type === 'value') {
element.value = value;
}
}
// Setup two-way binding for inputs
if (type === 'value' && element.tagName === 'INPUT') {
element.addEventListener('input', (e) => {
this.model.set(key, e.target.value);
});
}
});
}
showError(error) {
const errorElement = this.element.querySelector(
`[data-error="${error.key}"]`
);
if (errorElement) {
errorElement.textContent = error.error;
errorElement.style.display = 'block';
}
}
}
// Usage examples
const eventEmitter = new EventEmitter();
eventEmitter.on('user:login', (user) => {
console.log(`User ${user.name} logged in`);
});
eventEmitter.on('user:login', (user) => {
console.log(`Welcome back, ${user.name}!`);
});
eventEmitter.emit('user:login', { name: 'John Doe', id: 123 });
// Store example
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_COUNT':
return { ...state, count: action.payload };
default:
return state;
}
};
const store = new Store({ count: 0 }, counterReducer);
store.subscribe((data) => {
console.log('State changed:', data.currentState);
});
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'SET_COUNT', payload: 10 });
// Model-View example
const userModel = new Model({ name: '', email: '', age: 0 });
userModel.setValidator('email', (value) => {
return /\S+@\S+\.\S+/.test(value);
});
userModel.setValidator('age', (value) => {
return value >= 0 && value <= 120;
});
// const userView = new View(userModel, document.getElementById('user-form'));
// userView.bind('name', '[data-field="name"]', 'text');
// userView.bind('email', '[data-field="email"]', 'text');
userModel.set('name', 'John Doe');
userModel.set('email', 'john@example.com');
userModel.set('age', 30);
Conclusion
Design patterns provide proven solutions to common programming problems and help create more maintainable, flexible, and understandable code. The key is knowing when and how to apply these patterns appropriately. Start with the most common patterns like Singleton, Factory, Observer, and Module patterns, then gradually incorporate others as your applications become more complex. Remember that patterns should solve actual problems in your code, not be used just for the sake of using them.