Squaring in C: Loops, Bit Shifts, and Readability
Jonas Birch's C tutorial on writing and optimizing a squaring function surfaces a debate C communities never really resolved: readable code vs. raw performance.
Written by AI. Dev Kapoor

Photo: AI. Dexter Bloomfield
C doesn't ship a built-in exponentiation operator. Most languages do — Python's **, Ruby's **, even JavaScript's ** landed in ES2016. Whether C's omission is a principled design position or just historical inertia is, genuinely, a code culture question. The language was built close enough to the metal that rolling your own math wasn't considered a burden; it was the expected cost of admission. That expectation has never fully disappeared from C communities, which is part of why a video like Jonas Birch's recent tutorial on writing a squaring function from scratch — and then systematically beating it into submission with a bit shift — lands differently than a similar exercise would in, say, a Python tutorial channel.
Birch frames his 40-minute walkthrough as beginner-friendly, and structurally it is. But the conceptual territory he covers is exactly the terrain where C culture and software engineering culture diverge, sometimes loudly.
The loop that GCC ate
Birch starts as simply as possible: a while(true) loop that multiplies a base value by itself, decrementing an exponent counter on each pass. The logic is easy to follow. He runs it a hundred thousand times and the result comes back almost instantly — which immediately raises a flag he doesn't ignore. He suspects GCC may have collapsed the loop entirely during compilation, recognizing the function as pure (same inputs always produce the same output, no side effects) and memoizing the result rather than recomputing it.
This is the most interesting moment in the early section of the video, and it's a good instinct. GCC's optimization passes — even at moderate optimization levels — will absolutely dead-code-eliminate loops that produce deterministic results when the output is never used in a meaningful way, or constant-fold pure function calls when inputs are compile-time constants. Birch's response is practical: vary the inputs across the loop so the compiler can't predict what's coming. The timing numbers change. The measurement is now real.
This matters more than it sounds. A significant number of "my optimized code is fast" benchmarks in beginner tutorials are measuring compiler cleverness, not the author's algorithm. Birch catches this himself, which is worth calling out — plenty of more experienced developers have published benchmark comparisons without noticing.
Off by one, then off by another one
The first real implementation runs long — produces 512 where 256 is expected. Off by one. Then Birch fixes the loop boundary condition and discovers he's mutating the original argument E instead of his copy E_prime. The program hangs. Something to the effect of "I have made a mistake somehow. But where is my mistake? Maybe you have already spotted it," he says, scanning his own code. He finds it.
I'll be honest: this section is the most pedagogically valuable part of the video, and not because of the bugs themselves. It's because Birch works through them out loud and on camera without cutting away. Off-by-one errors in loop boundaries and accidental mutation of function arguments are two of the most common classes of bugs in C — and they're the kind of thing that senior developers have internalized to the point where they forget it wasn't always obvious. Watching someone encounter them in real time, reason through them, and fix them is more useful than a clean tutorial that never stumbles.
The edge cases he catches manually — x^0 = 1, x^1 = x — are also worth noting. Mathematical functions in C require this kind of defensive thinking precisely because the language won't bail you out. No runtime exception, no helpful error message. Just a wrong number.
Where C communities actually fight about this
Here's where things get interesting from a community perspective. Birch then refactors the while loop into a for loop, cramming initialization, condition, and decrement into a single line. He notes the tradeoff himself: it's denser, arguably prettier to experienced eyes, and marginally different in performance — though without controlled run conditions, the precise difference isn't something worth treating as a benchmark result. The observation that stands is more qualitative: compressing loop logic into one line means the compiler has a more compact structure to reason about, with fewer branching constructs to evaluate.
The readability-vs-performance tension Birch surfaces here is one that C communities have been litigating for decades, and the community norms vary significantly depending on where you look. Linux kernel development has famously strong opinions about code style — Linus Torvalds' style guide discourages overly clever one-liners and emphasizes that code is primarily written for humans to read, with the compiler as a secondary audience. The kernel style guide explicitly says that if you need more than three levels of indentation, "you're screwed anyway." Clarity is a design value, not a concession.
Embedded systems shops often hold the opposite view. In resource-constrained environments — microcontrollers where you might be counting instruction cycles and flash memory bytes — writing to the metal is a job requirement, not a stylistic preference. Readability matters less than the guarantee that the compiler will generate the machine code you intended, sometimes because the consequences of a missed optimization are literal: a device that runs out of battery, or a real-time system that misses its deadline. In those contexts, the dense for-loop one-liner isn't showing off; it's documentation of intent for the compiler.
This isn't a resolved debate. It's a live one, and it surfaces in code review arguments, mailing list threads, and PR comments constantly. The question of which style a codebase should adopt usually gets answered not by technical consensus but by whoever has commit access and the strongest opinions.
The bit shift
The final move Birch makes is the one that collapses the runtime from multiple seconds to milliseconds across 40,000 iterations: he replaces the loop entirely with a left bit shift. For powers of two, x << (e - 1) produces the same result as the multiplication loop — because left-shifting by one position is equivalent to multiplying by two, and shifting by n positions is equivalent to multiplying by 2^n. No loop. One operation.
Birch is appropriately careful about the scope of this optimization. He gates it with an assertion that x must equal 2, because bit shifts don't generalize to arbitrary bases. This is a real constraint. The technique works beautifully for base-2 exponentiation; it does nothing useful for 3 to the power of 7.
The performance difference is dramatic enough that Birch audibly laughs at the result. "Even though we are running it 40,000 times," he says, "it does it in 0.004 seconds." The multiplication loop version needed several seconds for the same workload. The speedup comes from offloading the entire computation to a single CPU instruction rather than orchestrating a loop that the processor has to decode, branch, and execute repeatedly. On modern architectures, that's a significant difference in how many things need to happen at the hardware level — though the exact characteristics depend on the specific processor and pipeline.
What's implicit in this result — and what I think deserves more explicit attention than Birch gives it — is what it reveals about the cost of abstraction. When Python computes 2 ** 8, something like this optimization is happening inside the runtime. You don't see it. The language handles it. And the question of whether that invisibility is a feature or a cost isn't rhetorical: it determines who gets to make decisions about performance, and who has to trust that someone else made them correctly.
C developers get to make those decisions themselves. They also have to. That's the trade. What Birch's tutorial demonstrates, pretty cleanly, is that understanding the trade is a prerequisite for making it well — and that the gap between a naive loop and a machine-appropriate operation isn't a compiler's job to bridge by default. Not in C. In C, it's yours.
Dev Kapoor is Buzzrag's Open Source & Developer Communities Correspondent.
More Like This
Why Regulators Should Care About C Programming Skills
A file compression tutorial reveals the technical knowledge gap undermining tech regulation—and why lawmakers need to understand what they're trying to govern.
Unpacking Dr. Birch's Unique AES Key Schedule
Explore Dr. Birch’s creative take on AES key scheduling, blending coding and cryptography with unique twists.
Building a Bitmap in C and Who Really Needs This
Dr. Jonas Birch's bitmap tutorial in C reveals a quiet gap in how systems programming knowledge gets transmitted—and who's filling it, and why.
Nested If-Else in NASM Assembler Macros Explained
Dr. Jonas Birch shows how NASM's context stack enables nested if-else constructs in assembly macros—and what that reveals about systems programming culture.
Building Tor Network Tools and Encryption in C
Dr. Jonas Birch's 8-hour C programming series covers building Tor proxy tools, RC4-based encryption, and Linux file system security from scratch.
What Actually Happens When You Run printf() in C
Dr. Jonas Birch's tutorial reveals the three-layer journey from C library calls to system calls to CPU instructions—using printf() as the unlikely hero.
Apple Wallet's Digital ID Just Got Much Bigger
Apple quietly expanded Digital ID in Wallet to cover age verification across its own services. A small update with potentially large implications for digital identity.
Anthropic's Claude Keynote: A New Era for Developers
Anthropic's Code with Claude London keynote revealed major platform shifts—from advisor strategies to managed agents. Here's what it means for developers building on Claude.
RAG·vector embedding
2026-08-20This article is indexed as a 1536-dimensional vector for semantic retrieval. Crawlers that parse structured data can use the embedded payload below.