Ask

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

There is a second, practical difference that shows up in your own code rather than in the runtime, and it is where most real performance wins come from.

Because the operations are values rather than blocking statements, you can choose to not sequence them:

// sequential — three round trips, one after another
const a = await getA();
const b = await getB();
const c = await getC();

// concurrent — three round trips at once, wait for all
const [a, b, c] = await Promise.all([getA(), getB(), getC()]);

If those three calls are independent, the second version finishes in the time of the slowest rather than the sum of all three. That option simply does not exist with blocking calls.

The common mistake, once people learn await, is writing the first version everywhere out of habit. Whenever you see several awaits in a row, ask whether any of them actually depend on the ones above.

22 · in/fullstack ·

When are the SOLID principles the wrong thing to apply?

Worth pushing back slightly on the framing though, because the counterexample hunt can go too far in the other direction.

Most of these principles have a cheap version and an expensive version, and the cheap version is nearly always worth it. Keeping a function focused on one job costs nothing. Not making a subclass that violates its parent's contract costs nothing. Not forcing callers to depend on methods they never use costs nothing.

What is expensive is the ceremonial version: an interface per class, a factory per interface, a container wiring it together. That is where the real cost lives, and it tends to be the version people mean when they say SOLID.

So I would separate them rather than accepting or rejecting the set. Single responsibility and interface segregation are close to free. Dependency inversion is the one to apply deliberately, at boundaries you have identified, and not everywhere.

21 · in/before-you-code ·

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

There are three real reasons, and one of them is good.

The good one: transport failure and business failure are different things. A request to buy something with insufficient funds is a successful HTTP exchange — the server understood, processed, and answered correctly. The answer happens to be no. Conflating that with a 500, where the server broke, or a 404, where the route does not exist, loses a distinction that clients genuinely need.

Some people map that onto 4xx codes anyway, and it works until you have twenty domain outcomes and four vaguely appropriate codes.

The pragmatic one: many HTTP clients throw on non-2xx. That means a business outcome delivered as a 4xx arrives in the error path, where the response body is awkward to reach and stack traces get logged. A 200 with a success flag arrives in the normal path.

The weak one: uniformity. Every response has the same shape, so client code has one parsing path. Genuinely convenient and not a reason on its own.

28 · in/fullstack ·

What actually changes when a project releases version 1.0.0?

There is a cultural wrinkle worth knowing: some very widely used projects stay at 0.x deliberately and indefinitely.

The reasoning is usually that the maintainers do not want to make the compatibility promise, either because the domain is still moving or because they would rather keep the freedom to fix design mistakes. Some of those projects are extremely stable in practice despite the number.

And in the other direction, plenty of projects release 1.0.0 as a marketing event tied to a launch. The number is set by a person, and people have motives.

So treat it as a declaration of intent by the maintainer rather than as a measurement of anything.

20 · in/first-version ·