Thursday, November 12, 2026

Unbounded Loops

[ Editor's note: I wrote this because I forgot, again :-( ]

I’ve written this before, but it is always worth repeating.

Unbounded loops are bad. If there is a bounded loop that can be used instead, use that instead.

Code really has two major flow-of-control constructs. One is conditional execution. You execute the code “sometimes”. We see that as ‘if’ statements, but there are all sorts of variations on that theme.

The other is loops. Loops come in two flavours, bounded and unbounded. A bounded loop is one that has the number of iterations effectively set in advance. A good example is a ‘for’ statement, with a variable called ‘i’. It starts at 0 and finishes at some ‘size’. The 0..size is the bounds for that loop. It will “always” stay within them.

Sometimes we don’t have bounds. A REPL is a good example. As long as the user keeps typing in text, the loop will continue to run. At some point, the user types in something special that triggers the code to ‘break’ the loop. Could be ‘exit’ or ‘quit’ or ‘EOF’ or something. It could just be a dynamic terminal triggered by the end of work. There are plenty of other places, like communication protocols, where there are often unbounded loops. But all of them are special.

Sometimes, people encode their loops as unbounded, even though they are essentially bounded. If encoding it as a bounded loop is only slightly less convenient, then it should be encoded as a bounded loop. Being clever with loops is really bad.

The reason for this is that it is self-describing. If you hit an unbounded loop, you might have to search around through a whack load of spaghetti to find its bounds. That’s a huge waste of time.

Another good reason is computational complexity. If you are coding loops, you really, really, really need to know how long they are going to take. If it loops on 3 or 4 things, you can quickly ignore it, but if it might loop on a million things, you can’t just chuck code like that out into production and hope for the best. One day it will fail, and people will be angry about it. If it can loop ‘that hard,’ then at bare minimum you’ll need some sort of testing to be in place so that you have the certainty that it won’t be you causing a denial of service attack on the system.

So, if you quickly read the code, and you can instantaneously pick up all of the bounds, then often in your head you can have a reasonable degree of certainty that when this code gets out there in the world, its behaviour will not sufficiently degrade from the behaviour you’ve been seeing while you worked on it in development.

That certainty is everything.

You have become an expert in programming because the code you write does only and exactly what it should do relative to your expectations. Obviously, that doesn’t mean it is “right”; it just means that when it runs, you will not be surprised, and you really don’t want surprises, do you?

Thursday, November 5, 2026

Difficult Problems

In software, these days, if there is a fleet of existing interoperating systems, it is most likely that when they were developed, they focused on solving the majority of the easy problems.

Often, it is also the case that, in doing that specific work, they made any related difficult problems far more difficult.

What this usually means is that if things are not working smoothly, then there are probably no longer any easy solutions that would fix the mess. The development has painted itself into a corner.

Anything that would make significant progress in correcting some or all of these deficiencies is going to be extremely difficult now.

Why?

The obvious point is that the current solution is fragmented; e.g. there is more than one interconnected system. The reason they are fragmented is that people kept tearing off smaller parts and solving those. That behaviour is a side effect of people zeroing in on the easy stuff and then trying to solve only their bit in isolation. Thus you get a bunch of systems where interconnection is an afterthought.

If you have a space where there are a lot of these smaller independent fragments, not only do they, as separate pieces, cause artificial complexity, but also their focus, as tunnelled from the larger whole, forces them to skip over difficulties. That is, they are so focused on just getting their thing done that they do not care about the whole thing, so if they hit something difficult while doing their thing, they punt that out of their space. This leaves the space between all these systems filled with all of these punted difficulties. When this has been the approach for a while, most of the easy stuff is gone, and what remains is darn scary. The overall complexity of the Frankenstein mess is orders of magnitude higher than any sort of unified, difficult solution.

What to do?

Really, you have to go back over the mess and identify the difficulties. Pick a big one, start there, and grow it out to cover back over the easy stuff. That is, if hyper-focusing on solving simple things got you into this mess, then only hyper-focusing on difficult things will get you out of it.

Most difficult problems in software aren’t really that difficult; they are just difficult in a very restricted context. If you pull up and out a bit, there is probably a known way to get just that problem solved correctly. It definitely won’t be a cakewalk, but it is still likely solvable.

Given that, you take this difficult problem, a known thorn in everyone’s side, and you write up both a specification of the problem itself and a design for something that would fix it. With those, you see what other existing parts of other systems they now cover. Then, if you are sure that would really fix the difficult issues, you work out a slow, phased transition to get that difficult core into place, and gradually extend it out to everything else. If you keep at this, eventually everything else disappears, and what you have left is one single integrated, fully functional, “difficult” solution that works smoothly.

I’ll say it again. Simple solutions do not solve difficult problems; they only fragment them into bigger messes. If the problem is difficult, the solution needs to be at least as difficult. There is no getting around this. You cannot avoid difficult problems; you can only ignore them for a while. I’ll call this the first law of solutions.

Thursday, October 29, 2026

Little Things

There are a bunch of little things -- habits -- that I’ve learned over the years that tend to make coding smoother and the results much better.

The primary one is to do as much as possible in advance of the actual coding. Make sure the environment is set up and working, including disks, peripherals, and furniture. Lay a good, solid foundation for things like logging, errors, config, secrets, and security. Get all of that out of the way first, not later as it is needed. When you sit down to write the core code, the last thing you want is distractions, so preparing your working environment to be able to get into that state of higher focus and stay there for longer is a key to being able to do a lot more things a whole lot faster.

The second one is to leave no stone unturned. That is, if you have questions about what or why you are coding, go answer those questions now. The answers don’t get better if you wait, and the lack of them always threatens to turn your labours into pure make-work. If you don’t know, then find out. Don’t assume, ignore, or procrastinate. Find out now.

Another one is to use way too many functions. Way too many. I use functions as a way to name clumps of code. It helps a lot. Even though there are a lot of functions, they shouldn’t end up with crazy or obfuscated names. Generally, they are just some verb as it operates on some input nouns. One word is preferable; two is acceptable. More than that is suspicious. Anything cryptic is offensive.

Functions, instead of comments, as a way to document what you are doing makes it way easier to review the code later and convince yourself that it really is doing what you thought it was doing. Also, they act as the primitives of decomposition. You break down the problem as far as you can put it into a bunch of small functions, but no further. An added bonus is that they make it easier to refactor. They are a powerful construct; use them wisely.

On that note, splitting out the pre-conditions and the core logic is big too. The first part of any function is there just to make sure the second part cannot get messed up. So, get all the asserts, error handling, little calcs, etc. out of the way at the top. Put every piece of data you will need later into its own unique variable; do not be shy. Similarly, use the language to clump stuff that must stay together all together. Clumping stuff saves on a lot of stupid typos.

Don’t waste time on makework, but also don’t be afraid to radically refactor some large chunk of code or a bad feature if it turns out that you were mistaken. If the logic is upside down, for example, and you persist rather than byte the bullet and correct it right now, it is just a time vacuum.

Make everything readable; spend effort on those tiny details. Partly because it shows you care, but also because those pesky things will hide lurking problems. Expect to have to revisit your own code in a few months, when you are too busy with other things; try to make that moment a lot easier.

Be terrified of names. They shouldn’t be robotic; there is no mindless convention for creating them. You really have to go out and find out what the parts are actually called, both technically and in the domain. Misnaming something is a magnet for bugs. If the poor name propagates all over the place, it can cause grief for decades. Give it the right name; put a lot of thought and effort into searching out that right name. Don’t be creative; that is usually just a failed shortcut.

Labour over your loops. Conditional logic is messy, but looping can be bloat. Don’t loop if you don’t have to; don’t bury the loops even if you think it's pretty. Split them out; you should be able to justify each and every one of them. Get rid of them whenever you can. Looping is where every performance problem begins and ends. Loops are not your friends; they are just the cost of doing business. Respect their power to make your evening awful.

Pull back. If it feels like you shouldn’t be spending 50 hours grinding out boilerplate, it is because you don’t need to be spending 50 hours grinding out boilerplate. There is a better way; you just have to calm down and find it. It’s exactly these types of moments where you learn to properly generalize or abstract stuff. The code would have been so similar that it is painful to type it out, but there is a way to get the same results, just smarter. Some techniques are easy, some take years to master, but all of them will keep you out of trouble. So, if it feels like you shouldn’t continue down this ugly brute force path, then don’t. Let that instinct push you into doing a search for a better way. It is out there.

Don’t be clever, be smart. I know’s tempting to find some clever little bit of syntactic sugar that can save a few characters here or there, but instead of being smart, it’s just a landmine. You or someone else will accidentally stumble over it and blow off their leg, usually in the middle of a production disaster or when there are -200 hours left on the clock. It almost always pays to be explicit, just not when it is tedious boilerplate. But otherwise, type it out in full. Eat that little expense, because later you will appreciate that you did.

The most important thing to understand is that although the code is not the system, the code itself is super important because it is your strongest way of controlling exactly what the system does, and you always need to control that to match everyone else’s expectations. It’s universally true that as the size of the code grows, the style, formatting, organization, consistency, and details get more and more important. A tiny demo can be messy, but a massive mission-critical system cannot. To be effective, you must be certain, and to be certain, you have to learn to be precise. That way, when everyone’s back is against the wall and you have to dive in and save the ship, you can go in and do exactly that, with no negative side effects. Perfect code is the dream, but the ability to perfect code quickly and reliably is good too. Don’t set yourself up to fail.