How do you implement a singleton pattern in JavaScript?
Assesses fundamental understanding of JavaScript conventions, runtime behavior, and memory/performance considerations.
Hiring managers look for precision, avoidance of ambiguous jargon, and ability to explain trade-offs under real production conditions.
The singleton pattern ensures a class has only one instance and provides a global access point to it.
Classic singleton with closure:
const Singleton = (function() {
let instance;
function createInstance() {
const object = {
name: 'Singleton',
data: []
};
return object;
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
ES6 class singleton:
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = [];
Singleton.instance = this;
}
addData(value) {
this.data.push(value);
}
getData() {
return this.data;
}
}
const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true
Module singleton (simplest):
// config.js
class Config {
constructor() {
this.settings = {};
}
set(key, value) {
this.settings[key] = value;
}
get(key) {
return this.settings[key];
}
}
export default new Config(); // Export single instance
// usage.js
import config from './config.js';
config.set('apiUrl', 'https://api.example.com');
Singleton with WeakMap (private instance):
const Singleton = (function() {
const instances = new WeakMap();
class Singleton {
constructor(key) {
if (instances.has(key)) {
return instances.get(key);
}
this.data = [];
instances.set(key, this);
}
}
return Singleton;
})();
Database connection singleton:
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.connection = null;
DatabaseConnection.instance = this;
}
connect(connectionString) {
if (!this.connection) {
this.connection = {
connectionString,
connected: true,
queries: []
};
}
return this.connection;
}
disconnect() {
if (this.connection) {
this.connection.connected = false;
this.connection = null;
}
}
query(sql) {
if (this.connection?.connected) {
this.connection.queries.push(sql);
return `Executing: ${sql}`;
}
throw new Error('Not connected');
}
}
const db1 = new DatabaseConnection();
db1.connect('mongodb://localhost:27017');
const db2 = new DatabaseConnection();
console.log(db1 === db2); // true
console.log(db2.query('SELECT * FROM users')); // Works
Candidate Response Strategy & Interview Tips
- Start with a concise one-sentence summary: Deliver a direct, confident answer first before expanding into nuances.
- Demonstrate real-world trade-offs: Discuss where this approach excels and when you would avoid it in production systems.
- Discuss complexity & edge cases: Proactively explain time/space complexity or boundary conditions (null values, scale limits).
- Prepare for interviewer follow-ups: Technical hiring panels frequently probe deeper into concurrency, backward compatibility, or alternative libraries.