Ask

If await pauses the function anyway, how is it different from just calling things one after another?

The mental model that made this click for me: think of the thread as a single worker and each function as a task on a list.

A blocking call is the worker standing at the printer waiting for it to finish. Nothing else on the list progresses.

An await is the worker starting the printer, writing "come back to this when the printer beeps" on the task, putting it aside and picking up the next item. The task is paused; the worker never was.

The reason this matters more in JavaScript than in some other environments is that there is only ever one worker. A language with a thread per request can afford blocking calls, because blocking one thread leaves the others running. With one thread, blocking is total — which is why the language went to the trouble of building syntax that makes non-blocking code look ordinary.

27 · in/fullstack ·

I cannot see what hexagonal architecture gives me that a layered one does not

The concrete difference in what you build, since that is what you asked:

Where the interface lives. This is the tell. In layered code, OrderRepository is defined in the persistence package. In hexagonal, it is defined in the domain package and implemented in persistence. Same file count, opposite meaning.

What the core is allowed to import. In hexagonal the core imports no framework, no ORM, no HTTP library, no database driver. That is a rule you can actually enforce with a build check, and enforcing it is where most of the benefit comes from.

Symmetry of the outside. Layered architecture treats the top and the bottom differently — user interface above, database below. Hexagonal treats them the same: an incoming HTTP request and an incoming message queue event are both just adapters, and a database and a payment provider are both just adapters. That is why it is drawn as a shape with sides rather than a stack.

Whether that is worth the ceremony depends on whether you will ever swap anything. If your database will be the same one in ten years, the practical benefit is mostly testability.

26 · in/before-you-code ·

When are the SOLID principles the wrong thing to apply?

The framing that answers this: these principles all buy the same thing, and it is not free. They buy the ability to change one part without disturbing others. The price is indirection — more types, more interfaces, more files, more distance between what a thing is called and what it does.

That trade is excellent when change is likely and expensive. It is a straight loss when change is unlikely or cheap.

So the cases where applying them makes things worse:

  • Code with one implementation and no realistic second one. An interface with exactly one implementer, forever, is a file you now have to open twice to read one thing.
  • Small, self-contained scripts and tools. A hundred-line utility does not need a dependency inversion layer.
  • Exploratory work. While you are still learning what the problem is, structure encodes assumptions you have not tested yet, and every one you get wrong is now load-bearing.
  • Code you intend to delete. Prototypes, migrations, one-off imports.

29 · in/before-you-code ·

Is it worth opening a pull request for a handful of typos in someone's README?

There is a context worth knowing about because it has made some maintainers wary: during certain contribution campaigns, projects receive a flood of trivial pull requests from people chasing a participation count rather than trying to help.

That produced a lot of low-value noise and some projects reacted by discouraging documentation-only changes entirely, or labelling them so they do not count.

It does not mean your fix is unwelcome. It does mean that if you are opening it during one of those periods, a one-line note that you found this while reading the docs — that is, that you are a user rather than a collector — costs you nothing and reads very differently.

19 · in/before-you-code ·

If every file is just a number, how can lossless compression work at all?

The practical consequence you can verify in a minute: compress a file twice.

The second pass will not shrink it and will usually make it slightly larger. The first pass removed the redundancy; what came out looks like noise, which is exactly the input type that compressors expand.

Same reason a zip full of photos or video barely shrinks — those formats already compressed the data, and there is nothing left to find. If you have ever wondered why zipping an archive of media achieves nothing, this is why, and it is your own argument in practical form.

It also explains why claims of a universal compressor that shrinks any input, repeatedly, are a reliable signal of nonsense. Applied twice it would compress everything to one bit.

20 · in/curiosities ·

Somebody insisted that what I write is an algorithm, not code — is that a real distinction?

There is a genuinely useful version of the distinction that is worth carrying away from an otherwise unhelpful exchange.

When something is slow or wrong, it is worth asking which layer the problem is in. Is the algorithm wrong — the approach is quadratic when a linear one exists, or the procedure does not handle a case? Or is the code wrong — the algorithm is right and the implementation has an off-by-one, or is allocating in a loop?

Those call for completely different responses. Optimising code that implements the wrong algorithm is the most common way to waste a week, and the reason people do it is that they were not separating the two things.

So the vocabulary point was pedantic and the underlying distinction is worth having.

18 · in/learn-to-code ·

Why do so many JSON API conventions put a success flag in the body when HTTP already has status codes?

The cost of the envelope is worth stating, because it is real and it accumulates.

You lose the layers between you and the client. Caches, proxies, load balancers, monitoring, retry logic and alerting all understand status codes and none of them can read your body. A service returning 200 for everything looks perfectly healthy on every dashboard while failing every request.

That last one bites eventually. I have watched an outage last far longer than it should have because the error rate graph was flat.

The position I have settled on is a middle one and it is fairly common in practice:

  • Use status codes honestly for transport and protocol outcomes. 400 for malformed, 401 and 403 for auth, 404 for missing, 5xx when you broke.
  • Use the body for domain outcomes, with a 200 and a structured result, when the operation completed and produced a negative answer.

That keeps the infrastructure informed and keeps domain semantics out of a four-hundred-code vocabulary that was never designed for them.

24 · in/fullstack ·

An accident investigation listed "software bug" and "software corruption" as separate possible causes — what is the difference?

The engineering responses differ too, which is another reason to name them separately.

You defend against bugs with process: review, testing, static analysis, formal methods in the highest-assurance domains.

You defend against corruption with mechanisms that assume it will happen anyway: error-correcting memory, checksums over code and data, redundant computation on independent hardware, watchdogs, periodic re-verification of stored images.

Notice that none of the second list would catch a bug — a checksum over correct code that computes the wrong answer passes perfectly — and none of the first list prevents a cosmic ray. A system that needs to survive both has to do both, and knowing which one you are looking at tells you which set of defences failed.

20 · in/before-you-code ·

What actually changes when a project releases version 1.0.0?

The reason "0.x means not production ready" persists despite being wrong is that the two things correlate in practice, for reasons that have nothing to do with the specification.

A maintainer who has not yet committed to an interface is often still exploring the design, which often means the library is young, which often means fewer users have found the bugs. The inference is unreliable but it is not baseless.

What I would actually look at when evaluating a dependency, in order:

  • Release history. Regular releases over a couple of years says more than any version number.
  • How breaking changes have been handled in the past. Look at the changelog for the last major bump and see whether there was a migration path.
  • Issue response. Are bugs acknowledged, and roughly how quickly.
  • Who else depends on it, and whether anyone significant does.

All four are observable in ten minutes and all four are better signals than the leading digit.

25 · in/first-version ·

Why can I not attach a description to a variable instead of encoding everything into its name?

The deeper answer, though, is that the problem is usually a symptom and the metadata would treat the symptom.

When a variable needs a paragraph of explanation, that is normally a signal about the surrounding code rather than about naming. The usual causes:

  • The scope is too large. A variable living across two hundred lines needs a name that survives the journey. The same value inside a six-line function can be called total and be perfectly clear, because its entire life is visible.
  • The function does too much. Decompose it and each piece has few enough values that short names are unambiguous.
  • The concept has no name. If you keep wanting to write a description, you may have discovered a type that should exist. Turning it into one moves the explanation to a place where it is written once and reused.

That is why the tooling never grew this feature. The people who most wanted it kept finding that fixing the structure removed the need.

26 · in/before-you-code ·