Why performance matters
A slow bot frustrates users. A bot that crashes from memory overuse is worse. Performance optimisation is not about squeezing every millisecond out of your code. It is about ensuring your bot runs reliably within its allocated resources, responds to commands quickly, and does not degrade over time.
On MonkeyBytes, your bot has 1 GB of RAM and 150% CPU. Most bots use a fraction of this. But if your bot has a memory leak, inefficient caching, or blocking operations, even generous resources will not save it. Fix the code first, upgrade hosting second.
Memory management
Finding memory leaks
A memory leak is when your bot allocates memory that is never released. Over hours or days, memory usage climbs until the process crashes. Common causes in Discord bots:
- Growing collections: Storing data in arrays or maps that grow without bounds. A message logger that saves every message to an array will eventually exhaust memory.
- Event listener accumulation: Adding event listeners in a loop or on every command without removing old ones.
- Unreleased timers: Creating
setIntervalorsetTimeoutcalls without clearing them when they are no longer needed. - Cache without limits: Caching API responses or database results without a maximum size or TTL (time to live).
Monitoring memory usage
// Node.js - log memory every 5 minutes
setInterval(() => {
const used = process.memoryUsage();
console.log(`Memory: RSS ${Math.round(used.rss / 1024 / 1024)}MB | Heap ${Math.round(used.heapUsed / 1024 / 1024)}MB`);
}, 5 * 60 * 1000);
# Python - log memory every 5 minutes
import psutil, os, asyncio
async def log_memory():
while True:
process = psutil.Process(os.getpid())
mb = process.memory_info().rss / 1024 / 1024
print(f'Memory: {mb:.1f}MB')
await asyncio.sleep(300)
If memory usage climbs steadily without levelling off, you have a leak. Look at what was added or changed since the last stable version.
Reducing cache memory
Discord libraries cache guild data, channels, members, and messages by default. For most bots, you do not need all of this data cached.
// discord.js - limit cache sizes
const { Client, GatewayIntentBits, Options } = require('discord.js');
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
makeCache: Options.cacheWithLimits({
MessageManager: 50,
GuildMemberManager: {
maxSize: 200,
keepOverLimit: (member) => member.id === client.user.id
},
PresenceManager: 0,
ReactionManager: 0,
ReactionUserManager: 0,
GuildEmojiManager: 0,
GuildStickerManager: 0
}),
sweepers: {
messages: {
interval: 300, // Check every 5 minutes
lifetime: 600 // Remove messages older than 10 minutes
}
}
});
# discord.py - limit intents to reduce cache
intents = discord.Intents.default()
intents.presences = False # Don't cache presence data
intents.members = False # Don't cache member lists
# Only enable what your bot actually uses
CPU optimisation
Avoid blocking the event loop
Discord bots run on an event loop. Any operation that blocks the loop prevents your bot from processing new events, responding to commands, or maintaining its gateway connection.
Common blocking operations:
- Synchronous file reads with
fs.readFileSync(usefs.promises.readFile) - CPU-heavy computations like image processing or encryption
time.sleep()in Python (useawait asyncio.sleep())- Large JSON parsing on the main thread
Batch API calls
Making many individual API calls is slower and more likely to trigger rate limits than batching. If you need to send messages to multiple channels, queue them and send with appropriate delays rather than firing them all simultaneously.
// Bad: rapid individual calls
for (const channel of channels) {
await channel.send('Announcement'); // Sequential, slow
}
// Better: rate-limited batch
const delay = (ms) => new Promise(r => setTimeout(r, ms));
for (const channel of channels) {
channel.send('Announcement').catch(console.error);
await delay(1000); // 1 second between sends
}
Response time optimisation
Defer replies for slow operations
Discord gives you 3 seconds to respond to a slash command interaction. If your command needs more time (database queries, API calls, image generation), defer the reply immediately:
// discord.js
client.on('interactionCreate', async (interaction) => {
if (interaction.commandName === 'stats') {
await interaction.deferReply(); // Acknowledge immediately
const data = await fetchStats(); // Slow operation
await interaction.editReply({ content: `Stats: ${data}` });
}
});
# discord.py
@bot.slash_command()
async def stats(ctx):
await ctx.defer() # Acknowledge immediately
data = await fetch_stats() # Slow operation
await ctx.respond(f'Stats: {data}')
Cache expensive operations
If a command fetches the same data repeatedly, cache the result:
// Simple TTL cache
const cache = new Map();
const CACHE_TTL = 60000; // 1 minute
function getCached(key, fetchFn) {
const cached = cache.get(key);
if (cached && Date.now() - cached.time < CACHE_TTL) {
return cached.data;
}
const data = fetchFn();
cache.set(key, { data, time: Date.now() });
return data;
}
Database performance
Index your queries
If your bot queries by user ID or guild ID frequently, add database indexes:
-- SQLite
CREATE INDEX IF NOT EXISTS idx_warnings_user ON warnings(user_id);
CREATE INDEX IF NOT EXISTS idx_warnings_guild ON warnings(guild_id);
Use connection pooling
For external databases (MongoDB, PostgreSQL), use connection pools instead of opening a new connection per query. Most ORMs and drivers handle this automatically, but verify your configuration.
Query only what you need
// Bad: fetch all columns and filter in code
const allWarnings = await db.all('SELECT * FROM warnings');
const userWarnings = allWarnings.filter(w => w.user_id === userId);
// Good: filter in the database
const userWarnings = await db.all('SELECT reason, created_at FROM warnings WHERE user_id = ?', userId);
For database selection and setup, see our database guide.
Performance checklist
- Memory usage is stable over 24 hours (no steady climb)
- Cache sizes are limited with maximum entries or TTL
- Only needed gateway intents are enabled
- No synchronous blocking operations on the event loop
- Slash commands defer replies for operations over 1 second
- Database queries use indexes on frequently queried columns
- API calls are batched with appropriate rate limiting
- Event listeners are not accumulated in loops
- Timers and intervals are cleared when no longer needed
- Logging does not write sensitive data or excessive output
When to optimise vs when to upgrade
Optimise first, upgrade second. Most performance issues in Discord bots are code problems, not resource problems. A bot with a memory leak will crash on a 4 GB VPS just as it crashes on a 1 GB free host. It just takes longer.
Upgrade your hosting when your bot is optimised and still consistently uses more than 70–80% of available resources. For scaling guidance, read our scaling guide. For hosting options, compare costs in our cost breakdown or evaluate VPS hosting in our VPS comparison.
For related topics, see our troubleshooting guide for debugging crashes, or the uptime monitoring guide to track your bot's health over time.