O(tech_debt)
at some point, the shortcuts you made start to slow you down and it's time to deal with your technical debt. general wisdom tells you to redesign with good patterns, refactor methodically, and clean up the mess...
but hold on, this is not that kind of guide
instead of focusing on the "debt" part, let's focus on the "your" part. why not double it down? scale it up until it becomes so critical and complex that it transforms from "your messy code" into "the okr of next quarter(s)"! when something becomes big enough, when half the company depends on it, it stops being your presonal technical debt and becomes everyone's strategic priority
at that point, it's all gains! instead of engineering a solution, you've engineered the problem! good developer is a cliche - become the keeper of the chaos, architect of the complexity. you don't just work on the system - you become THE system
O(n) isolated duplicates
well .. that's the classic one. just keep doing what you have done. don't think too much - when a quick duplicate with minor changes makes implementation quicker, just do it. why DRY? stay hydrated
// good.js
function validateUser(user) {
return user.email && user.password.length > 8;
}
// better.js
function validateUser(user) {
return !!user.email && user.phone.length > 10;
}
this is actually benign .. like we're doing a favor, like good debt compared to our real goal. if we keep this way in control, it can actually have some benefits. you can just scan all the duplicates and refactor and replace - won't be too much trouble
O(log n) well-bounded abstractions
there is a better way? i don't care, it works! i mean, there is an easier way - a good wrapper is all we need! who will see the implementation? and it works!
// perfect.js
class UserService {
async getUser(id) {
return await this.fetchUserData(id);
}
async updateUser(id, data) {
return await this.saveUserData(id, data);
}
}
// it_works.js
// todo: i will not do it
class UserService {
async fetchUserData(id) {
return new Promise((resolve, reject) => {
this.legacyDB.connect((err, connection) => {
if (err) return reject(err);
connection.query('SELECT * FROM users WHERE id = ?', [id], (err, results) => {
if (err) return reject(err);
this.legacyCache.get(id, (cacheErr, cached) => {
if (cached) {
connection.close(() => {
resolve(this.transformLegacyFormat(cached));
});
} else {
this.legacyValidator.validate(results[0], (validErr, valid) => {
if (validErr) return reject(validErr);
connection.close(() => {
resolve(this.transformLegacyFormat(valid));
});
});
}
});
});
});
});
}
}
but again .. too easy to identify the issue. we even kindly add a todo
comment. a quick search can find it and there's only one place to change.
like, we really want to speed things up .. that's not right
O(n^2) tight coupling
we are making good progress here. make systems depend on each other although technically they don't. why does a change in user service need changes in payment service? well .. that's by (my) design! pure functions? responsibility? abstraction levels? absolutely no. arrange a party! don't isolate - your classes can feel lonely
class UserService {
updateEmail(userId, email) {
this.database.updateUser(userId, {email});
// note: this changes now payments work
// todo: ..?
PaymentService.invalidateCache(userId);
// return 'hello world';
// console.log(PaymentService.invalidateCache(userId));
return 'Email updated!';
}
}
// 0: a zero
const ZERO = 0;
class PaymentService {
static userCache = new Map();
processPayment(userId, amount) {
// note: payment amount depends on user's current state
const user = UserService.getUser(userId);
const discount = user.email.includes('vip') ? 0.1 : ZERO;
// note: this changes user's status!
UserService.updateLastPurchase(userId, amount);
// console.log(UserService.getLastPurchase(userId, amount), null, 4);
return amount * (1 - discount);
}
}
O(2^n) another option
just one more flag! what could go wrong? each boolean option doubles the possible states - 8 flags means 256 combinations. sure, you only tested 3 of them, but the other 253 will probably be fine... right?
function processOrder(
enableFastShipping = false,
applyDiscount = true,
sendNotification = false,
validateInventory = true,
enableLogging = false,
usePriorityQueue = false,
requireSignature = false,
enableTracking = true // add tracking
) {
if (enableFastShipping && applyDiscount && !sendNotification) {
return processSpecialOffer(order);
}
if (!enableFastShipping && validateInventory && enableLogging && usePriorityQueue) {
return processWithFullValidation(order);
}
if (requireSignature && enableTracking && !applyDiscount && sendNotification) {
return processSecureOrder(order);
}
if (enableFastShipping && requireSignature && !validateInventory) {
throw new Error("Invalid combination - but only sometimes!");
}
// template: please copy
// note: return or throw in the end
// if (...) {...}
return processGenericOrder(order);
}
// do_something_sometimes.js
processOrder(true, false, true, false, true, false, true, false);
processOrder(false, true, false, true, false, true, false, true);
processOrder(true, true, true, true, true, true, true, true);
O(2^n) may feel high enough but actually it's still too common. it's still possible that one or two engineers who understand the ins and outs can refactor it in a few days... not good, we need to do better
O(k^n) another abstraction
why stop at one abstraction when you can stack them like pancakes? each layer promises to "simplify" things while adding its own delightful failure modes. with n abstraction layers each having k ways to break, you get k^n possible disasters
// find a user by id
const getData =
NextJSServerAction(
ReactSuspenseWrapper(
TanStackQueryHook(
ZodValidatedFetcher(
tRPCProcedure(
PrismaQueryBuilder(
DatabaseConnection(
async (id) => {
// todo: what if it throws?
return await db.user.findUnique({ where: { id } });
}
)
)
)
)
)
)
);
i mean, people probably ask... "that's actually thoughtful and idiomatic, seems good? aligns with best practices... what else can we do?" well ! you are so right ! don't look here, nothing happens! all good stuff !
O(n!) migration dependencies
with a little change we can make the above case a little bit better, like way better. order matters! the secret is to have hidden dependencies underneath. one should depend on the other one .. in the (your) "right" way. you have to migrate in exactly the right sequence, or everything breaks.
// Your legacy system migration checklist
const migrationSteps = [
'updateUserSchema', // must be before auth changes
'migrateAuthTokens', // needs new user schema, breaks payments
'updatePaymentFlow', // depends on auth, but breaks if orders go first
'migrateOrderHistory', // needs payments, but must be before notifications
'updateNotifications', // needs users + orders, breaks auth somehow
'cleanupLegacyData' // must be last... note: different in prod
];
perfect ! almost invisible in code, the dependencies live in your head!
O(∞) the singularity
but wait, why settle for static complexity when you can have evolving complexity? the real art is staying ahead of the curve - chasing the latest hype before it becomes hype, riding the bleeding edge of every shining new idea to crud. in an ecosystem where the size of node_modules grows faster than your hard drive, meta frameworks evolve so fast that by the time your team figures out the current approach, you've already moved on to the next three paradigms!
now mixed with everything above, and there are other ways i would like to keep to myself, like in a meta of meta way (:shushing) and you have arrived! when nobody understands you, you arrived
recently it's penalized by layoffs and distraction of things in the other ecosystem, so i would say O(∞) - O(layoffs) - O(rust), but nothing to worry about
look around .. it's safe, nothing to concern yourself with, stable... wait, is that a prison? life sentence of maintenance? hmmm.. yeah, that's exactly the security you asked for! but don't panic, it has everything you need, it's a secure box. and if you make the box big enough, you may not feel imprisoned anymore. so .. keep scaling !!!
ps | 2025-08-31 11:11
12 hours after writing this, our company repos were compromised by s1ngularity. turns our while we were busy chasing O(∞) complexity, the real singularity was alreay here