<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ron Northcutt</title>
    <description>The latest articles on DEV Community by Ron Northcutt (@rlnorthcutt).</description>
    <link>https://dev.to/rlnorthcutt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1165280%2F3938e619-df7d-42ff-89c4-6133c1b70991.jpeg</url>
      <title>DEV Community: Ron Northcutt</title>
      <link>https://dev.to/rlnorthcutt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rlnorthcutt"/>
    <language>en</language>
    <item>
      <title>HAProxy Holds Your Tokens for 206ms? Here's What the Default Is Actually Doing (And Why)</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Mon, 21 Sep 2026 12:37:00 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/haproxys-200-ms-llm-token-delay-is-a-smart-default-heres-when-to-change-it-3eah</link>
      <guid>https://dev.to/rlnorthcutt/haproxys-200-ms-llm-token-delay-is-a-smart-default-heres-when-to-change-it-3eah</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: I work at HAProxy, so I'm probably a bit biased. Still, this is an accurate look at the situation. A fun little deep dive, actually. Regardless, this is my own opinion/research, not official from the company.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A recent &lt;a href="https://dev.to/remdore/nginx-streams-your-tokens-fine-haproxy-holds-them-for-206ms-10p2"&gt;benchmark post&lt;/a&gt; made the rounds with a table that &lt;em&gt;seems&lt;/em&gt; bad for HAProxy. Four proxies sat in front of a scripted LLM emitter sending one tiny frame every 50 milliseconds. Through nginx, Caddy, and Traefik, tokens arrived 2 to 3 milliseconds after the proxy got them, one per read, indistinguishable from no proxy at all. Through HAProxy, the first token showed up &lt;em&gt;206 milliseconds late&lt;/em&gt; and the rest arrived in bursts of five with no gap between them.&lt;/p&gt;

&lt;p&gt;The post's verdict: HAProxy buffers your tokens. That sounded unusual for such a performant tool, so I decided to dig into it a bit. &lt;/p&gt;

&lt;p&gt;Here is what I found: &lt;strong&gt;nothing in HAProxy is collecting tokens in a bucket&lt;/strong&gt;. The delay comes from HAProxy telling the &lt;em&gt;kernel&lt;/em&gt; to wait for more data, on purpose, documented almost to the millisecond, and reversible with one line of config. And the answer to "why is it on by default" is that for most of the traffic HAProxy carries, the default is the faster choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hold happens in the kernel
&lt;/h2&gt;

&lt;p&gt;The background is &lt;a href="https://en.wikipedia.org/wiki/Nagle's_algorithm" rel="noopener noreferrer"&gt;Nagle's algorithm&lt;/a&gt;, named for John Nagle and described in RFC 896 back in 1984: a sender holding small amounts of data should wait for a full packet's worth, or for the receiver to acknowledge the previous packet, before putting another tiny packet on the wire. The reasoning is arithmetic. Without batching, a 60-byte write pays a header-sized envelope, and those headers can double the bytes transmitted, or worse as frames shrink.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nagle isn't the actor here, though&lt;/strong&gt;. HAProxy already sets &lt;code&gt;TCP_NODELAY&lt;/code&gt; on its TCP sockets. What holds the tokens is a second, explicit mechanism: while a response body is still being received, HAProxy tags every client-facing write with the kernel's &lt;code&gt;MSG_MORE&lt;/code&gt; flag, which tells the kernel "more data is coming, hold this." In the 3.4 source the article tested, the H1 mux sets the tag on every in-flight body write (&lt;code&gt;src/mux_h1.c&lt;/code&gt;, via &lt;code&gt;CO_SFL_MSG_MORE&lt;/code&gt;) and clears it when the body ends. The &lt;a href="https://docs.haproxy.org/2.2/configuration.html#4.2-option%20http-no-delay" rel="noopener noreferrer"&gt;configuration manual&lt;/a&gt; describes the effect exactly: the system &lt;em&gt;"waits for enough data to be available in order to only send full packets. Typical delays are around 200 ms per round trip."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A token stream never fills a packet. Sixty bytes every 50ms, with a round trip sitting between each one, so the corked writes sit in the kernel until the tag clears or a timer cycle releases them. The tokens leave in batches, late by a few cycles, and they reach the client mashed together: five frames, zero gap, 206 ms. That matches the benchmark numbers.&lt;/p&gt;

&lt;p&gt;First, this is &lt;strong&gt;not&lt;/strong&gt; application buffering. HAProxy forwards the response body as it arrives. The waiting happens in the kernel's send path, not in a proxy buffer. &lt;/p&gt;

&lt;p&gt;Second, the benchmark author says this himself: frames-per-read is a property of the stream shape, not a permanent fact about the proxy. Same proxy, 1.1 KB frames instead of 60-byte ones, and the delay drops to 53 ms with near-zero coalescing. The batching only bites when frames are small relative to a packet, which is, unfortunately, the exact shape of an LLM token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batching wins for bulk traffic
&lt;/h2&gt;

&lt;p&gt;For the traffic HAProxy was built to carry, batching is a measurable win, and the costs fall on workloads that never notice.&lt;/p&gt;

&lt;p&gt;HAProxy's home turf is bulk HTTP at high concurrency: API responses, uploads, downloads, and everything in between, at request rates where packet counts and syscall overhead show up directly on the CPU graph. Every tiny write batched into a bigger packet is a syscall saved, a header not transmitted, and a few microseconds returned to the event loop. The HAProxy &lt;a href="https://docs.haproxy.org/2.2/management.html#:~:text=definitely%20remains%0Aenabled%20when%20forwarding%20an%20HTTP%20body%20(and%20this%20contributes%20to%20the%20performance%0Aimprovement%20there%20by%20reducing%20the%20number%20of%20packets)" rel="noopener noreferrer"&gt;troubleshooting guide&lt;/a&gt; states the trade plainly: Nagle "&lt;em&gt;definitely remains enabled when forwarding an HTTP body (and this contributes to the performance improvement there by reducing the number of packets).&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;For those workloads the latency cost is invisible, because a download or an API response doesn't care about 40 ms of accumulated batching once per connection; the payload arrives in bulk regardless, and nobody waiting on it can tell the difference.&lt;/p&gt;

&lt;p&gt;The design also thought about interactive traffic. The batching is &lt;strong&gt;skipped automatically&lt;/strong&gt; in pure TCP mode and in tunnels. WebSockets and CONNECT requests are explicitly documented as unaffected. The default was tuned with a clear picture of what "interactive" meant at the time: a tunnel, an upgraded socket, or someone pushing a protocol through HTTP that the HTTP spec never intended.&lt;/p&gt;

&lt;p&gt;Which brings us to the core issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSE slipped through the model
&lt;/h2&gt;

&lt;p&gt;Server-sent events are plain chunked HTTP. There's no upgrade, no tunnel, nothing to trip the existing exemptions. To HAProxy, an SSE response is an ordinary body that happens to dribble in, so every client-facing write carries the hold tag.&lt;/p&gt;

&lt;p&gt;The benchmark measured 206. The manual's "&lt;em&gt;around 200 ms per round trip&lt;/em&gt;" predicted it because its just math.&lt;/p&gt;

&lt;p&gt;None of this is hidden, and none of it is an accident. The defaults were tuned for the traffic HAProxy has carried for most of its life: bulk responses at high concurrency, workloads where waiting to fill a packet costs nothing and saves real CPU. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token streaming inverts that trade&lt;/strong&gt;. The frames are tiny, the stream runs for minutes instead of milliseconds, and the wait lands between a model and a user watching the screen. A default shaped for one kind of traffic cannot also be shaped for its opposite, and bending it until it fits both is how you end up with slower defaults for everyone.&lt;/p&gt;

&lt;p&gt;This is precisely why the adjustment exists. &lt;code&gt;option http-no-delay&lt;/code&gt; is the designed escape hatch for workloads whose trade-offs differ from the common case, documented in the same manual as the default itself. The system works the way a configurable system should: &lt;strong&gt;fast by default for the many, one line to match the few&lt;/strong&gt;. The benchmark's real contribution is showing which default fits which traffic, and for token streaming the answer is one line long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The adjustment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend llm-inference
    option http-no-delay
    server inference 10.0.0.5:8000 check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line, in the backend carrying the stream. It tells HAProxy to stop tagging the writes, so the kernel never holds them, and the manual describes the result directly: "&lt;em&gt;all such optimizations will be disabled in order to make the exchanges as fast as possible.&lt;/em&gt;" Frames leave when they arrive.&lt;/p&gt;

&lt;p&gt;This isn't just documentation. The benchmark was reproduced on loopback with the same stream shape the article used, 60-byte chunked SSE events every 50 ms, running a local HAProxy 3.2 after reading the 3.4 source the article tested:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;setup&lt;/th&gt;
&lt;th&gt;first event&lt;/th&gt;
&lt;th&gt;frames per read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;direct to origin&lt;/td&gt;
&lt;td&gt;1 ms&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;through HAProxy, default&lt;/td&gt;
&lt;td&gt;212 ms&lt;/td&gt;
&lt;td&gt;5.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;through HAProxy, &lt;code&gt;option http-no-delay&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1 ms&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same shape as the original numbers, and the one line erases the delay completely: first token back to 1 ms, one frame per read, indistinguishable from no proxy. The benchmark author listed "&lt;em&gt;whether &lt;code&gt;option http-no-delay&lt;/code&gt; removes it&lt;/em&gt;" as a test he'd still like to run. Consider this cell run, with credit to his rig for the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning it off costs something
&lt;/h2&gt;

&lt;p&gt;With Nagle off, every frame goes out as its own write, which means one packet and one syscall per frame instead of one per full segment. On a 50 ms token cadence that's roughly 20 packets a second per stream, which no individual user will ever notice. &lt;/p&gt;

&lt;p&gt;Across tens of thousands of concurrent streams on a busy edge, however, it becomes a real and measurable bump in CPU consumption and packet overhead, and the overhead compounds anywhere the network path is slow or congested.&lt;/p&gt;

&lt;p&gt;The manual warns about this too: the option "&lt;em&gt;should never be used by default&lt;/em&gt;," and its cost "&lt;em&gt;may significantly lower performance in high latency environments&lt;/em&gt;," where every small packet now pays the full round trip alone instead of sharing it with its neighbors.&lt;/p&gt;

&lt;p&gt;So the move is &lt;strong&gt;not to paste it into a global defaults&lt;/strong&gt; section. Scope it to the backends that stream. Let the bulk traffic keep the batching.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to change it
&lt;/h2&gt;

&lt;p&gt;A rule of thumb that applies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the payload &lt;em&gt;is&lt;/em&gt; the message, turn it off. If the payload is bulk, leave it on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tokens, chat deltas, live logs, trading ticks, anything where a human or a model is waiting on each frame: those want &lt;code&gt;option http-no-delay&lt;/code&gt;. File downloads, API JSON responses, images: keep the default and keep the efficiency.&lt;/p&gt;

&lt;p&gt;When it's unclear which side the traffic falls on, measure it. The benchmark harness from the original post is a good template, and the author's write-up of the fourteen ways he broke it before trusting it is worth reading regardless of which proxy runs in front of the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nginx made the opposite bet
&lt;/h2&gt;

&lt;p&gt;It's fair to ask, because nginx ships the reverse wager. Its &lt;code&gt;tcp_nodelay&lt;/code&gt; directive defaults to on, so small writes leave immediately. In exchange, nginx buffers more aggressively at the application layer (&lt;code&gt;proxy_buffering&lt;/code&gt; defaults to on). HAProxy does the opposite: it forwards responses almost immediately and asks the kernel to batch the body writes instead.&lt;/p&gt;

&lt;p&gt;The benchmark shows both bets working. nginx's buffering cost about 50 ms only in a deliberately nasty case: a 328 KB payload pushed to a client that sleeps 200 ms between reads. For prompt clients on small streams, it costs nothing. HAProxy's batching costs 200 ms only for small-frame streams. For everything else, it's free throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neither default is wrong&lt;/strong&gt;. nginx is friendly out of the box for streaming and pays slightly more overhead on bulk workloads, while HAProxy is leaner on bulk HTTP at scale and needs one line of config for streams. If the product is a stream of tokens, both are available at once: HAProxy's throughput and nginx's latency, for the price of one config line.&lt;/p&gt;

&lt;h2&gt;
  
  
  A knob, not a wall
&lt;/h2&gt;

&lt;p&gt;A default is an opinion about the common case, and HAProxy's defaults are opinions worth having. They favor throughput, low CPU, and safety at scale, and the numbers behind them show up in every large deployment. &lt;/p&gt;

&lt;p&gt;The 206 ms number is real, it's documented, it's reproducible, and it's a knob, not a wall. If tokens are streaming through HAProxy today, add the line, re-run the benchmark, and take the milliseconds back.&lt;/p&gt;

&lt;p&gt;Thanks to the &lt;a href="https://dev.to/remdore"&gt;original author&lt;/a&gt; for the careful measurement work. This post started as a technical review of it, and the rig's honesty (killed findings, one-shot cells, published configs) made the review easy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@jahan_photobox?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Jahanzeb Ahsan&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-blurry-photo-of-a-person-riding-a-bike-ZbFoi92fyzY?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>haproxy</category>
      <category>llm</category>
      <category>sse</category>
      <category>performance</category>
    </item>
    <item>
      <title>Raw PHP, opcache, JIT, and AOT: what actually happens to your code</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Tue, 15 Sep 2026 14:07:57 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/raw-php-opcache-jit-and-aot-what-actually-happens-to-your-code-3752</link>
      <guid>https://dev.to/rlnorthcutt/raw-php-opcache-jit-and-aot-what-actually-happens-to-your-code-3752</guid>
      <description>&lt;p&gt;A few weeks ago, I posted a &lt;a href="https://www.linkedin.com/posts/rlnorthcutt_typephp-activity-7498803750862528512-jj9H" rel="noopener noreferrer"&gt;carousel on LinkedIn&lt;/a&gt; about &lt;a href="https://github.com/swoole/typephp" rel="noopener noreferrer"&gt;TypePHP&lt;/a&gt;, the ahead-of-time compiler the Swoole team open sourced. It turns PHP source into a native executable that starts on its own, with no PHP CLI and no separate interpreter process involved.&lt;/p&gt;

&lt;p&gt;The response surprised me. Almost nobody wanted to argue about the benchmarks. What people kept asking, in one form or another, was: &lt;em&gt;isn't this just JIT? Doesn't opcache already do this? Where does this fit with what I'm already running?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thnking about it, I realized my own mental model had gone fuzzy. I knew opcache was important and I knew JIT existed, but I couldn't have clearly explained the difference to someone who asked. So I went back and worked it out.&lt;/p&gt;

&lt;p&gt;This is that refresher, written up for anyone else who wants it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;If you've got two minutes, here's the whole thing.&lt;/p&gt;

&lt;p&gt;PHP has to do two separate jobs before your code produces a result. &lt;/p&gt;

&lt;p&gt;1) First it has to &lt;strong&gt;understand&lt;/strong&gt; your code, which means reading the text and turning it into instructions. &lt;br&gt;
2) Then it has to &lt;strong&gt;execute&lt;/strong&gt; those instructions. The four approaches each attack a different part of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw PHP&lt;/strong&gt; does both jobs on every single request. It reads your files, compiles them, runs them, and throws all the work away. Then the next request comes in and it does the whole thing again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opcache&lt;/strong&gt; fixes the first job. It saves the compiled instructions in memory so PHP stops re-reading your source on every request. This is the single biggest performance win available to a PHP app and it costs you nothing but memory. If you take one thing from this article, take this one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JIT&lt;/strong&gt; attacks the second job, with limited success. While your code is running, it watches for hot spots and converts them to native machine code on the fly. It produces impressive numbers on math-heavy code and almost nothing on a normal web request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AOT&lt;/strong&gt; attacks the second job by changing the rules. It compiles your PHP all the way to a native binary before you ever deploy it. It goes far faster than JIT can, but only because it gives up part of the PHP language to get there.&lt;/p&gt;

&lt;p&gt;The first three are about making your existing app faster. AOT is more about redefining what you can build with PHP at all... and that's the part I find really interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  First, what PHP is doing with your code
&lt;/h2&gt;

&lt;p&gt;When a request hits your app, PHP reads your &lt;code&gt;.php&lt;/code&gt; files as plain text. It parses them, checks the syntax, and compiles them into something called &lt;strong&gt;opcodes&lt;/strong&gt;. Opcodes are small, simple instructions, roughly the PHP equivalent of assembly. Something like &lt;code&gt;$a + $b&lt;/code&gt; becomes an ADD opcode with two operands.&lt;/p&gt;

&lt;p&gt;Then the &lt;strong&gt;Zend VM&lt;/strong&gt; takes over. The VM is a loop. It grabs the next opcode, figures out what it means, does it, and moves to the next one. Over and over until your script finishes.&lt;/p&gt;

&lt;p&gt;There are two specific things you need to keep in mind.&lt;/p&gt;

&lt;p&gt;The first is that PHP variables aren't raw values. They're &lt;strong&gt;zvals&lt;/strong&gt;, which are little containers that hold both the value and a tag saying what type it is. When the VM adds two variables, it has to check the tags first, because &lt;code&gt;$a + $b&lt;/code&gt; means something different for two integers than for two strings. That type check happens at runtime, every time, because PHP doesn't know the types ahead of time.&lt;/p&gt;

&lt;p&gt;The second is that all of this work gets discarded when the request ends. PHP's process model throws everything away and starts clean. That's actually a feature. It's why PHP is so hard to leak memory in and so forgiving of sloppy code, but it also means any work you do during a request has to be redone on the next one. And the next one. And the next one.&lt;/p&gt;

&lt;p&gt;These strategies offer different answers to the question: "which part of that can we stop repeating?"&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1s4uu0co7hjqootq5be.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1s4uu0co7hjqootq5be.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Raw PHP - goldfish memory
&lt;/h2&gt;

&lt;p&gt;This is PHP with nothing turned on. Every request: read the files, parse them, compile them, run the opcodes, throw it all away.&lt;/p&gt;

&lt;p&gt;Nobody should be running this on purpose in 2026, but it's the baseline and it's worth understanding why it's so bad.&lt;/p&gt;

&lt;p&gt;The problem isn't your code. The problem is your framework. A modern Drupal or Laravel install loads hundreds or thousands of PHP files to serve one page. Every one of those files has to be read off disk, tokenized, parsed, and compiled before a single line of your actual application logic runs. On a typical framework request, that setup work can eat more time than the work you actually care about.&lt;/p&gt;

&lt;p&gt;You'll still run into this sometimes. A misconfigured container image, a dev environment nobody set up properly, a hosting provider cutting corners. If an app feels inexplicably slow and the database looks fine, checking whether opcache is actually enabled is a good first move. &lt;/p&gt;




&lt;h2&gt;
  
  
  2. Opcache - caching is your friend
&lt;/h2&gt;

&lt;p&gt;Opcache fixes the obvious waste. The first time a file is requested, PHP compiles it as usual, but then opcache stores the resulting opcodes in shared memory. Every request after that skips straight to execution.&lt;/p&gt;

&lt;p&gt;Your source files stop being read. The parser stops running. All that setup work happens once instead of thousands of times a day.&lt;/p&gt;

&lt;p&gt;On a framework-heavy app this commonly gets you several times the throughput, which is an enormous return for a config flag. It's built into PHP, it's on by default in most modern builds, and the only real cost is a chunk of memory.&lt;/p&gt;

&lt;p&gt;But notice what opcache does &lt;strong&gt;not&lt;/strong&gt; do. The Zend VM is still there, still walking through opcodes one at a time, still checking zval types on every operation. Opcache removed the cost of &lt;em&gt;understanding&lt;/em&gt; your code. It did nothing about the cost of &lt;em&gt;running&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;however, a tight loop that does math a million times gets essentially nothing out of opcache. Opcache removed the cost of compiling that loop, which happens once. The time is going into the VM executing it a million times, and opcache never touches that part.&lt;/p&gt;

&lt;p&gt;That gap is what the next two approaches go after.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. JIT - good on paper, but...
&lt;/h2&gt;

&lt;p&gt;JIT stands for just-in-time compilation, and it arrived in PHP 8.0. It builds directly on top of opcache — it's not a separate thing you run instead, it's a layer that sits on top.&lt;/p&gt;

&lt;p&gt;The idea is that while your code is running, PHP watches which parts run most often. When something crosses a threshold, PHP compiles that section into real native machine code and runs the machine code instead of interpreting opcodes. The compilation happens &lt;em&gt;during&lt;/em&gt; execution, which is where "just in time" comes from.&lt;/p&gt;

&lt;p&gt;On paper this should be huge. In practice, for most web apps, it's roughly nothing. There are a few key reasons why.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP's dynamic types get in the way.
&lt;/h3&gt;

&lt;p&gt;The JIT wants to compile &lt;code&gt;$a + $b&lt;/code&gt; into a single machine instruction. To do that it needs to know that &lt;code&gt;$a&lt;/code&gt; and &lt;code&gt;$b&lt;/code&gt; are both integers. But PHP can't promise that because anything could have been assigned to those variables. So the JIT emits the fast machine code &lt;em&gt;plus&lt;/em&gt; a guard check that verifies the types are what it assumed. If the guard fails, it bails back to the interpreter. Those guards cost time, and they're everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  It has to preserve all of PHP's behavior.
&lt;/h3&gt;

&lt;p&gt;References, magic methods, error handlers, the ability to redefine things at runtime. The JIT can't optimize away anything that might be observable, and in PHP an awful lot is observable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The work doesn't survive.
&lt;/h3&gt;

&lt;p&gt;The compiled machine code lives in one worker process. When that process recycles, it's gone, and the next one has to warm up from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most web requests aren't CPU-bound anyway.
&lt;/h3&gt;

&lt;p&gt;A typical page load spends its time waiting on MySQL, waiting on Redis, waiting on an API. Making the PHP execution faster doesn't help when the PHP was already sitting around waiting.&lt;/p&gt;

&lt;p&gt;Where JIT genuinely does earn its keep is code that's actually doing arithmetic in a loop: image manipulation, numeric simulation, machine learning inference, statistical work, anything that grinds on numbers without touching the network. &lt;/p&gt;

&lt;p&gt;If that's your workload, turn it on and measure. If it isn't, JIT is a config option you can safely leave alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. AOT - now this is interesting
&lt;/h2&gt;

&lt;p&gt;AOT stands for &lt;strong&gt;ahead-of-time&lt;/strong&gt;. Instead of compiling while your program runs, it compiles before you deploy at all. That makes sense.&lt;/p&gt;

&lt;p&gt;TypePHP takes your PHP source, translates it into C++17, and hands that to gcc or clang. What comes out the other end is a native binary. No interpreter, no opcodes, no VM. Just machine code, the same as if you'd written the thing in C.&lt;/p&gt;

&lt;p&gt;And here's the part that matters: the reason it goes so much faster than JIT isn't that compiling beats interpreting. It's that &lt;strong&gt;AOT gets to know the types&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you gain
&lt;/h3&gt;

&lt;p&gt;By requiring you to write in a typed subset of PHP, the compiler can turn a PHP &lt;code&gt;int&lt;/code&gt; into an actual machine integer instead of a zval. No box, no tag, no runtime type check, no guard, no bailout path. Once the types are real, it can hand the whole program to a C++ optimizer that's had thirty years of work poured into it... inlining, loop unrolling, constant folding, vectorization, all of it, with full visibility into your code.&lt;/p&gt;

&lt;p&gt;That's where the impressive numbers come from. The project reports around 69x on a hundred-million-iteration pi calculation and roughly 135x on a recursive Fibonacci. Broader language benchmarks are more modest, at about 8x on &lt;code&gt;bench.php&lt;/code&gt; and 6.5x on &lt;code&gt;micro_bench.php&lt;/code&gt;. Not bad, that's for sure.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you lose
&lt;/h3&gt;

&lt;p&gt;The catch is exactly symmetrical to JIT's. JIT keeps the entire PHP language and accepts a performance ceiling. AOT breaks through the ceiling by &lt;strong&gt;giving up part of the language&lt;/strong&gt;. TypePHP compiles a defined subset and publishes the incompatibility list openly, which I respect. Global scope is declaration-only. Binary mode wants a &lt;code&gt;main()&lt;/code&gt; with a specific signature. Some dynamic reference and reflection patterns simply don't compile.&lt;/p&gt;

&lt;p&gt;So no, you can't compile Drupal core. But that was never really the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the fine print
&lt;/h3&gt;

&lt;p&gt;Binary mode produces an executable that starts directly, with no PHP CLI and no separate interpreter process. That's real and it's useful. But the executable still links &lt;code&gt;libphp&lt;/code&gt; and PHPX, and those have to ship in your deployment package. &lt;/p&gt;

&lt;p&gt;So, this isn't a statically linked Go binary you can scp anywhere and run. It's closer to any other compiled program with shared library dependencies where you ship the program and the libraries it needs.&lt;/p&gt;

&lt;p&gt;That's an important detail that I missed in the initial reporting, and not really "no PHP required." It's also the kind of thing worth knowing before you promise it to anyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  You don't have to compile all of it
&lt;/h3&gt;

&lt;p&gt;Here's the thing that isn't get talked about enough. Binary mode gets the attention, but TypePHP also has &lt;code&gt;-m ext&lt;/code&gt;, which outputs a &lt;code&gt;.so&lt;/code&gt; (or &lt;code&gt;.dll&lt;/code&gt;) that PHP loads as a normal extension.&lt;/p&gt;

&lt;p&gt;That inverts the whole question. You're not compiling your application. You're compiling a &lt;em&gt;piece&lt;/em&gt; of it, and the rest of your code base stays ordinary interpreted PHP that calls into it like it would any other extension.&lt;/p&gt;

&lt;p&gt;Think about where that lands in a large app. A router. A cache backend. A  template compiler. A serializer. A pricing or rules engine that runs on every request. In most mature code bases there's a small percentage of the code responsible for a large percentage of the CPU time, and it tends to be the stable, well-tested, rarely-touched part. &lt;strong&gt;That's exactly the profile you want for something you compile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PHP extensions have always been able to do this. The catch was that writing one meant writing C, which put it out of reach for basically every team that wasn't already maintaining an extension. Now the thing you write is PHP!&lt;/p&gt;

&lt;p&gt;So the realistic question isn't "can we compile Drupal." It's "which two percent of our code is hot enough to be worth compiling, and can we carve it out cleanly?" That's a much smaller, much more answerable question.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkh6duxazphmy9yteb5q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkh6duxazphmy9yteb5q4.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Opcache gets you a large, reliable win. JIT on top of it gets you approximately nothing on a normal web app, but sometimes it can help.&lt;/p&gt;

&lt;p&gt;The AOT bar has no length because there isn't an honest one to draw. Nobody can compile a whole framework app today, so "AOT throughput on a standard Laravel install" is not a number that exists. What you can say is that on whatever portion you do compile, you'd see somewhere between 6.5x and 135x depending on what that code is doing.&lt;/p&gt;

&lt;p&gt;Which is the real reason AOT sits awkwardly in a performance comparison. It isn't competing with opcache and JIT. It's answering a different question.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faf512afm30mvlraqhrxf.png" alt=" " width="800" height="450"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  So which one do you actually use?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Opcache is not optional.&lt;/strong&gt; &lt;br&gt;
Turn it on, give it enough memory to hold your whole codebase, and verify it's actually running in production. This is the highest-return thing on the list by a wide margin. If you do nothing else after reading this, go check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JIT is a targeted tool, not a general upgrade.&lt;/strong&gt; &lt;br&gt;
Enable it if your PHP is doing real computation, and measure before and after. Don't turn it on across the board expecting your web app to get faster, because it won't, and you'll have spent memory for nothing. The default for most applications is to leave it off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AOT is a different kind of decision entirely.&lt;/strong&gt; Don't evaluate it as "should we make our app faster." Evaluate it as "is PHP execution genuinely our bottleneck, or do we need to ship something PHP couldn't ship before?" If it's the first, you're looking at extension mode and a profiler, not a rewrite.&lt;/p&gt;

&lt;p&gt;That second part is probably the most useful in the near term. A native executable that starts on its own, with no PHP CLI to install and no vendor directory to ship, is a distribution capability that no amount of opcache or JIT tuning will ever give you. Neither will linking against a Rust library, or handing a customer something they can run without installing PHP first, or putting PHP code somewhere a PHP runtime was never going to fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Super cool, but doesn't change things that much ... yet
&lt;/h3&gt;

&lt;p&gt;Anyone running PHP apps probably doesn't need anything else right now. For a typical request that's waiting on a database, &lt;strong&gt;none of the four helps much beyond opcache&lt;/strong&gt;. If your p99 is dominated by queries, the answer is indexes and caching, not compilers. AOT matters when PHP execution itself is the bottleneck, or when the thing you want to ship isn't a website at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Four approaches, and they're not really competing with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw PHP&lt;/strong&gt; re-does all the work on every request. It's the baseline, and if you find yourself here it's a misconfiguration rather than a choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opcache&lt;/strong&gt; stops PHP re-reading and re-compiling your source. Several times the throughput for a config flag and some memory. Always on, no exceptions, this is where the biggest and cheapest win lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JIT&lt;/strong&gt; compiles hot code to machine code while your program runs, but PHP's dynamic types force it to hedge with guard checks, and the work vanishes when the worker recycles. Real gains on numeric code, near-zero on ordinary web requests. Reach for it deliberately and measure, because turning it on by default costs memory and buys most apps nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AOT&lt;/strong&gt; compiles everything up front and gets to eliminate the type checks entirely, which is where the 69x and 135x figures come from. The price is a restricted language subset, a build step, and a compatibility audit. &lt;/p&gt;

&lt;p&gt;You don't have to swallow it whole, though. Extension mode lets you compile one hot subsystem and leave everything else exactly as it is. Worth it when execution is genuinely your bottleneck, or when you need to ship something in a shape PHP could not produce before.&lt;/p&gt;

&lt;p&gt;The first three make your existing app cheaper to run. The fourth changes what you can point a PHP team at.&lt;/p&gt;

&lt;p&gt;That's why I don't think AOT belongs in the same conversation as opcache and JIT, even though it keeps landing there. Opcache and JIT are answers to "how fast is our website." AOT is an answer to "what can we build." Those are different questions.&lt;/p&gt;

&lt;p&gt;Whether TypePHP's supported subset grows fast enough to matter is genuinely open. But languages that find a way out of their original niche tend to stick around a lot longer than the ones that don't.&lt;/p&gt;

</description>
      <category>php</category>
      <category>performance</category>
      <category>webdev</category>
      <category>aot</category>
    </item>
    <item>
      <title>One wildcard cert and a script: local domains for every app you run</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:33:25 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/one-wildcard-cert-and-a-script-local-domains-for-every-app-you-run-4ked</link>
      <guid>https://dev.to/rlnorthcutt/one-wildcard-cert-and-a-script-local-domains-for-every-app-you-run-4ked</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: Yes, I created a shell script that will do all of this for you, so you can be lazy (see below). But I still recommend doing it at least once by hand. It's a great way to learn (or remember) and it's pretty quick. Learn it by hand once, and &lt;strong&gt;then&lt;/strong&gt; get lazy.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Part one got &lt;em&gt;&lt;a href="https://dev.to/rlnorthcutt/stop-typing-localhost-ports-custom-local-domains-with-haproxy-and-mkcert-fdf"&gt;one local app onto a local domain&lt;/a&gt;&lt;/em&gt; with a green padlock. That setup has a ceiling, and you hit it fast: every new app means a new cert, a new ACL, a new backend, and another trip through the same four commands.&lt;/p&gt;

&lt;p&gt;This post removes most of that. One cert covers every &lt;code&gt;.omni&lt;/code&gt; domain you'll ever create, the config pattern scales to as many apps as you want to run, and a script collapses the whole thing into one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  One wildcard cert for everything
&lt;/h2&gt;

&lt;p&gt;Generating a cert per app works, but you can skip that step forever with a single wildcard cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkcert &lt;span class="nt"&gt;-cert-file&lt;/span&gt; ~/.config/haproxy/certs/omni-wildcard.crt &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-key-file&lt;/span&gt; ~/.config/haproxy/certs/omni-wildcard.key &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="s2"&gt;"*.omni"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.config/haproxy/certs/omni-wildcard.crt &lt;span class="se"&gt;\&lt;/span&gt;
    ~/.config/haproxy/certs/omni-wildcard.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.config/haproxy/certs/omni-wildcard.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point the &lt;code&gt;crt&lt;/code&gt; line at &lt;code&gt;omni-wildcard.pem&lt;/code&gt; and every &lt;code&gt;app.omni&lt;/code&gt; you add is already trusted. Adding a new app drops to three steps: hosts entry, ACL + backend, reload. No new cert, ever.&lt;/p&gt;

&lt;p&gt;That next app is two lines in the frontend and two in a new backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    acl is_work req.hdr(host),host_only -i work.omni
    use_backend work_backend if is_work

backend work_backend
    server loopback 127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two things:&lt;/strong&gt;&lt;br&gt;
1) Name the pem something generic like &lt;code&gt;omni-wildcard.pem&lt;/code&gt;, since it now belongs to the whole setup instead of one app.&lt;br&gt;
2) The wildcard only matches one label deep: &lt;code&gt;*.omni&lt;/code&gt; covers &lt;code&gt;omnideck.omni&lt;/code&gt; and &lt;code&gt;work.omni&lt;/code&gt;, but not &lt;code&gt;api.omnideck.omni&lt;/code&gt;. If you nest subdomains, add them to the cert explicitly.&lt;/p&gt;


&lt;h2&gt;
  
  
  Putting it together: three instances at once
&lt;/h2&gt;

&lt;p&gt;Here's what this actually looks like day to day. I run three Omnideck instances with different configs and different model backends: one for work, one for personal projects, one for breaking things.&lt;/p&gt;

&lt;p&gt;Hosts entries first, one per instance, since the hosts file has no wildcards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
127.0.0.1 work.omni
127.0.0.1 home.omni
127.0.0.1 lab.omni
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wildcard cert from above covers all three. And the whole config, start to finish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global
    log stdout format raw local0

defaults
    log global
    mode http
    option httplog
    option forwardfor
    timeout connect 5s
    timeout client  1h
    timeout server  1h
    timeout tunnel  1h

frontend http_in
    bind *:80
    tcp-request connection reject if !{ src 127.0.0.0/8 ::1 }
    http-request redirect scheme https code 301

frontend https_in
    bind *:443 ssl crt /Users/you/.config/haproxy/certs/omni-wildcard.pem alpn h2,http/1.1

    tcp-request connection reject if !{ src 127.0.0.0/8 ::1 }

    http-request set-header X-Forwarded-Proto https

    acl is_work req.hdr(host),host_only -i work.omni
    acl is_home req.hdr(host),host_only -i home.omni
    acl is_lab  req.hdr(host),host_only -i lab.omni

    use_backend work_backend if is_work
    use_backend home_backend if is_home
    use_backend lab_backend  if is_lab

backend work_backend
    server loopback 127.0.0.1:46176

backend home_backend
    server loopback 127.0.0.1:46177

backend lab_backend
    server loopback 127.0.0.1:46178
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it, reload, and confirm all three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;haproxy &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/etc/haproxy.cfg
brew services restart haproxy

&lt;span class="k"&gt;for &lt;/span&gt;host &lt;span class="k"&gt;in &lt;/span&gt;work home lab&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;.omni -&amp;gt; %{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;.omni"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three &lt;code&gt;200&lt;/code&gt;s and you're done. A &lt;code&gt;503&lt;/code&gt; means the request reached HAProxy but no ACL matched, which is usually a typo in the hosts file or a missing &lt;code&gt;use_backend&lt;/code&gt; line. A connection refused means the instance behind that port isn't running.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: the &lt;code&gt;tcp-request connection reject&lt;/code&gt; lines carry over from part one, and they matter more here. Three unauthenticated instances behind a wildcard bind is three times the surface on conference wifi. See the FAQ if you want the stricter version.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Automate it with a script
&lt;/h2&gt;

&lt;p&gt;Doing this by hand for every new app still gets repetitive. This script wraps the hosts entry, the cert generation, and the HAProxy reload into one command:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://gist.github.com/rlnorthcutt/aa107620f1272a3df06fdbd427000c26" rel="noopener noreferrer"&gt;GitHub Gist: add-omni-domain.sh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./add-omni-domain.sh omnideck.omni 46176
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes a slightly different route on certs than the wildcard above. Instead of one cert for everything, it generates a cert per domain and points HAProxy's &lt;code&gt;crt&lt;/code&gt; at the whole directory, letting SNI pick the right one per request. Same outcome, no config edit either way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: This script requires sudo, so don't trust it blindly. Read it first, or at least have AI check it before you run it. Better yet, use it as a guide and build your own. Put in your own responses, make the emojis better, and extend it to do other cool things.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What else?
&lt;/h2&gt;

&lt;p&gt;That's the whole setup. Local apps answer to real domain names with HTTPS, and adding the next one takes a single command. I'm actually thinking about adding it as an optional step for the &lt;a href="https://github.com/omnideck-dev/cli" rel="noopener noreferrer"&gt;omnideck cli installer&lt;/a&gt;, but I'm not sure. Thoughts?&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can HAProxy route wildcard .omni domains?
&lt;/h3&gt;

&lt;p&gt;Yes. Generate a wildcard cert with &lt;code&gt;mkcert "*.omni"&lt;/code&gt;, point &lt;code&gt;crt&lt;/code&gt; at the combined &lt;code&gt;.pem&lt;/code&gt;, and match the &lt;code&gt;Host&lt;/code&gt; header with an ACL to send each subdomain to its own backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I stop editing /etc/hosts for every app?
&lt;/h3&gt;

&lt;p&gt;Yes, with dnsmasq. Install it via brew, add &lt;code&gt;address=/.omni/127.0.0.1&lt;/code&gt; to its config, and on macOS create &lt;code&gt;/etc/resolver/omni&lt;/code&gt; containing &lt;code&gt;nameserver 127.0.0.1&lt;/code&gt;. Every &lt;code&gt;.omni&lt;/code&gt; name then resolves without a hosts entry, and adding an app drops to ACL, backend, reload. Pair it with the wildcard cert and new apps cost you four lines of config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the reject rule enough, or should I bind to loopback?
&lt;/h3&gt;

&lt;p&gt;The reject rule is enough for a laptop you carry around. The port stays open and answers nothing, which is the practical outcome you want.&lt;/p&gt;

&lt;p&gt;If you'd rather the port never open at all, bind the interface instead of the wildcard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    bind 127.0.0.1:443 ssl crt /Users/you/.config/haproxy/certs/omni-wildcard.pem alpn h2,http/1.1
    bind [::1]:443 ssl crt /Users/you/.config/haproxy/certs/omni-wildcard.pem alpn h2,http/1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux the sysctl from part one already covers this. On macOS it won't work as your user, because Mojave's low-port exception applies only to wildcard binds. You'd need &lt;code&gt;sudo brew services start haproxy&lt;/code&gt;, which installs HAProxy as a system daemon rather than a user agent. That's a real tradeoff for a dev box, which is why the reject rule is the default in these posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I have to restart HAProxy every time I add an app?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;brew services restart&lt;/code&gt; is a full stop and start, so in-flight connections drop. On a dev box that's usually fine. If you're mid-stream on something you care about, &lt;code&gt;haproxy -sf $(pgrep haproxy)&lt;/code&gt; reloads with the new config and lets the old process finish what it started.&lt;/p&gt;

&lt;h3&gt;
  
  
  My agent run dies partway through. Is that the proxy?
&lt;/h3&gt;

&lt;p&gt;Probably, if it dies at a suspiciously round interval. HAProxy's default 50s client and server timeouts cut off idle SSE and WebSocket connections, and a streaming run looks idle to the proxy between chunks. The &lt;code&gt;1h&lt;/code&gt; timeouts in the config above, &lt;code&gt;timeout tunnel&lt;/code&gt; especially, are there for exactly this.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@homaappliances?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Homa Appliances&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/industrial-manufacturing-machine-control-panel-_XDK4naBbgw?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>haproxy</category>
      <category>omnideck</category>
      <category>tutorial</category>
      <category>localhost</category>
    </item>
    <item>
      <title>Stop typing localhost ports: custom local domains with HAProxy and mkcert</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Wed, 09 Sep 2026 12:18:45 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/stop-typing-localhost-ports-custom-local-domains-with-haproxy-and-mkcert-fdf</link>
      <guid>https://dev.to/rlnorthcutt/stop-typing-localhost-ports-custom-local-domains-with-haproxy-and-mkcert-fdf</guid>
      <description>&lt;p&gt;When you build local apps, typing port numbers gets old fast. &lt;a href="https://omnideck.dev" rel="noopener noreferrer"&gt;Omnideck&lt;/a&gt; runs on &lt;code&gt;localhost:46176&lt;/code&gt;. A Go microservice sits on another port. A scraper sits on a third. &lt;code&gt;127.0.0.1:46176&lt;/code&gt; works, but &lt;code&gt;https://omnideck.omni&lt;/code&gt; is cleaner and easier to remember.&lt;/p&gt;

&lt;p&gt;Plus... it's just cooler. That's not a huge reason to do it, but it doesn't hurt! This guide maps custom &lt;code&gt;.omni&lt;/code&gt; domains to local Omnideck instances, but you can use any pattern you like (&lt;code&gt;.mine&lt;/code&gt;, &lt;code&gt;.lab&lt;/code&gt;, &lt;code&gt;.grid&lt;/code&gt;, and so on).&lt;/p&gt;

&lt;p&gt;The stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HAProxy as the reverse proxy&lt;/li&gt;
&lt;li&gt;Homebrew for installs and services&lt;/li&gt;
&lt;li&gt;mkcert for local HTTPS&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: This is part 1. The next article covers how to use &lt;a href="https://dev.to/rlnorthcutt/one-wildcard-cert-and-a-script-local-domains-for-every-app-you-run-4ked"&gt;one wildcard cert for a simpler setup&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;macOS or Linux with Homebrew installed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo&lt;/code&gt; access to edit &lt;code&gt;/etc/hosts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your apps already listening on localhost ports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why do this?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Readable URLs.&lt;/strong&gt; &lt;code&gt;omnideck.omni&lt;/code&gt; beats remembering ports for five projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production parity.&lt;/strong&gt; Cookies, CORS, OAuth redirects, and HTTPS-only APIs behave differently on &lt;code&gt;localhost&lt;/code&gt; than on real domain names. Testing on a domain catches these early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple apps at once.&lt;/strong&gt; Run &lt;code&gt;work.omni&lt;/code&gt; and &lt;code&gt;home.omni&lt;/code&gt; side by side with no port juggling.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Homebrew?
&lt;/h2&gt;

&lt;p&gt;Homebrew keeps the proxy binary, config, and service in one place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One package manager for installs and updates. &lt;code&gt;brew install haproxy&lt;/code&gt; works on macOS and Linux.&lt;/li&gt;
&lt;li&gt;Managed services. &lt;code&gt;brew services start haproxy&lt;/code&gt; runs HAProxy in the background and starts it again when you log in.&lt;/li&gt;
&lt;li&gt;Config files live in &lt;code&gt;$(brew --prefix)/etc/&lt;/code&gt; instead of scattered across the system root.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: map the domain in /etc/hosts
&lt;/h2&gt;

&lt;p&gt;The hosts file maps hostnames to IP addresses. It has no concept of ports, so &lt;code&gt;127.0.0.1:46176&lt;/code&gt; is invalid there. Add the mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1 omnideck.omni"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that once. Running it again appends a duplicate line.&lt;/p&gt;

&lt;p&gt;Verify it resolves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 1 omnideck.omni
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a browser still shows the old page, flush the DNS cache or restart the browser. On macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dscacheutil &lt;span class="nt"&gt;-flushcache&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;killall &lt;span class="nt"&gt;-HUP&lt;/span&gt; mDNSResponder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: reverse proxy with HAProxy
&lt;/h2&gt;

&lt;p&gt;HAProxy listens on port 80 and forwards matching requests to &lt;code&gt;127.0.0.1:46176&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;haproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  On Linux
&lt;/h3&gt;

&lt;p&gt;In Linux, user services can't bind to ports below 1024 by default. So, you need to lower the limit to 80:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-w&lt;/span&gt; net.ipv4.ip_unprivileged_port_start&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That value resets on reboot.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;To keep it, put &lt;code&gt;net.ipv4.ip_unprivileged_port_start=80&lt;/code&gt; in &lt;code&gt;/etc/sysctl.d/99-ports.conf&lt;/code&gt; and run &lt;code&gt;sudo sysctl -p /etc/sysctl.d/99-ports.conf&lt;/code&gt;. Keep in mind that this lets any user process bind ports 80-1023, not just HAProxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  On macOS
&lt;/h3&gt;

&lt;p&gt;You can skip this step. Since Mojave, unprivileged processes can bind low ports, but only on a wildcard address: &lt;code&gt;bind *:80&lt;/code&gt; works, while &lt;code&gt;bind 127.0.0.1:80&lt;/code&gt; still returns permission denied. &lt;/p&gt;

&lt;p&gt;The tradeoff is that a wildcard bind answers on every interface, which the config below deals with.&lt;/p&gt;

&lt;h3&gt;
  
  
  HAProxy config
&lt;/h3&gt;

&lt;p&gt;Edit the config at &lt;code&gt;$(brew --prefix)/etc/haproxy.cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global
    log stdout format raw local0

defaults
    log global
    mode http
    option httplog
    option forwardfor
    timeout connect 5s
    timeout client  1h
    timeout server  1h
    timeout tunnel  1h

frontend http_in
    bind *:80

    # Answer only requests from this machine
    tcp-request connection reject if !{ src 127.0.0.0/8 ::1 }

    # host_only strips any port the browser appends to the Host header
    acl is_omnideck req.hdr(host),host_only -i omnideck.omni

    use_backend omnideck_backend if is_omnideck

backend omnideck_backend
    server loopback 127.0.0.1:46176
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;tcp-request connection reject&lt;/code&gt; line is important! You're binding a wildcard, so the proxy is reachable from &lt;em&gt;every device on whatever network you join&lt;/em&gt;, and the app behind it probably has no auth of its own. &lt;/p&gt;

&lt;p&gt;The rule drops non-local connections before the handshake even starts. The port still shows up in a scan, but nothing off your machine gets an answer. Drop that line if you actually want the apps reachable from a phone or a VM on your LAN. But do it on purpose.&lt;/p&gt;

&lt;p&gt;The long timeouts are deliberate. The usual 50s defaults are fine for page loads, but they cut off idle SSE and WebSocket connections, which is exactly what a streaming agent run looks like from the proxy's side. &lt;code&gt;timeout tunnel&lt;/code&gt; is the one that governs an established WebSocket.&lt;/p&gt;

&lt;p&gt;Check the config, then start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;haproxy &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/etc/haproxy.cfg
brew services start haproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://omnideck.omni
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an &lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt; line from your app, not a connection error.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: HTTPS with mkcert
&lt;/h2&gt;

&lt;p&gt;HTTPS removes browser warnings and lets you test features that require a secure context.&lt;/p&gt;

&lt;p&gt;Install mkcert and trust its local CA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;mkcert
mkcert &lt;span class="nt"&gt;-install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mkcert -install&lt;/code&gt; adds a root CA to your OS and browser trust stores once. After that you never import &lt;code&gt;.crt&lt;/code&gt; files into Chrome or Firefox by hand. One catch on both macOS and Linux: Firefox keeps its own trust store, so install &lt;code&gt;certutil&lt;/code&gt; first (&lt;code&gt;brew install nss&lt;/code&gt; or &lt;code&gt;apt install libnss3-tools&lt;/code&gt;) or Firefox will keep warning you.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NOTE: You can also just manually upload the cert into your browser.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generate a certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/haproxy/certs
mkcert &lt;span class="nt"&gt;-cert-file&lt;/span&gt; ~/.config/haproxy/certs/omnideck.crt &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nt"&gt;-key-file&lt;/span&gt; ~/.config/haproxy/certs/omnideck.key &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="s2"&gt;"omnideck.omni"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.config/haproxy/certs/omnideck.crt &lt;span class="se"&gt;\&lt;/span&gt;
    ~/.config/haproxy/certs/omnideck.key &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.config/haproxy/certs/omnideck.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HAProxy needs one combined &lt;code&gt;.pem&lt;/code&gt; file, certificate first, then key.&lt;/p&gt;

&lt;p&gt;Update the config for TLS termination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global
    log stdout format raw local0

defaults
    log global
    mode http
    option httplog
    option forwardfor
    timeout connect 5s
    timeout client  1h
    timeout server  1h
    timeout tunnel  1h

# Port 80: send everything to HTTPS
frontend http_in
    bind *:80
    tcp-request connection reject if !{ src 127.0.0.0/8 ::1 }
    http-request redirect scheme https code 301

# Port 443: TLS termination
frontend https_in
    bind *:443 ssl crt /Users/you/.config/haproxy/certs/omnideck.pem alpn h2,http/1.1

    tcp-request connection reject if !{ src 127.0.0.0/8 ::1 }

    # So the app knows it's behind TLS and builds https:// redirects
    http-request set-header X-Forwarded-Proto https

    acl is_omnideck req.hdr(host),host_only -i omnideck.omni
    use_backend omnideck_backend if is_omnideck

backend omnideck_backend
    server loopback 127.0.0.1:46176
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four notes on that block:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HAProxy does not expand &lt;code&gt;~&lt;/code&gt; in config paths. Run &lt;code&gt;echo ~/.config/haproxy/certs/omnideck.pem&lt;/code&gt; and paste the absolute path into the &lt;code&gt;crt&lt;/code&gt; line. On macOS that starts with &lt;code&gt;/Users/&lt;/code&gt;, on Linux with &lt;code&gt;/home/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alpn h2,http/1.1&lt;/code&gt; enables HTTP/2. It needs a TLS library with ALPN support, which means OpenSSL 1.0.2 or newer. Homebrew's HAProxy links against OpenSSL 3, so you're covered.&lt;/li&gt;
&lt;li&gt;Without &lt;code&gt;X-Forwarded-Proto&lt;/code&gt;, an app that builds absolute URLs will hand back &lt;code&gt;http://&lt;/code&gt; links and break the OAuth redirects you set this up to test in the first place.&lt;/li&gt;
&lt;li&gt;The reject rule now sits in both frontends. It runs at connection time, so a remote client gets dropped before the TLS handshake and never learns which certs you're holding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Linux users: port 443 is already covered by the sysctl setting from Step 2, so just reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew services restart haproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit &lt;code&gt;https://omnideck.omni&lt;/code&gt;. No warning, green padlock.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;One app, one domain, one padlock. Adding a second app means a second cert, a second ACL, and a second backend, which is fine once and tedious by the third time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/rlnorthcutt/one-wildcard-cert-and-a-script-local-domains-for-every-app-you-run-4ked"&gt;Part two fixes that&lt;/a&gt;: one wildcard cert for every &lt;code&gt;.omni&lt;/code&gt; domain you'll ever make, a full config running three Omnideck instances side by side, and a script that does the whole thing in one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why can't /etc/hosts take port numbers?
&lt;/h3&gt;

&lt;p&gt;It maps hostnames to IP addresses and nothing else. Port routing lives above DNS resolution, so it belongs in a proxy like HAProxy or nginx. See, silly things like this can teach us stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why .omni instead of .local or .dev?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.dev&lt;/code&gt; and &lt;code&gt;.app&lt;/code&gt; are on the HSTS preload list, so browsers demand valid public certificates. &lt;code&gt;.local&lt;/code&gt; is reserved for mDNS (Bonjour), which can add resolution delays. A made-up TLD like &lt;code&gt;.omni&lt;/code&gt; avoids both problems. If you want zero collision risk, use &lt;code&gt;.test&lt;/code&gt; (reserved by the IETF) or &lt;code&gt;.internal&lt;/code&gt; (reserved by ICANN in 2024 for exactly this purpose). I use &lt;code&gt;.omni&lt;/code&gt; because, again, it's cooler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my browser search instead of loading the site?
&lt;/h3&gt;

&lt;p&gt;Made-up TLDs aren't in the browser's public suffix list, so Chrome treats &lt;code&gt;omnideck.omni&lt;/code&gt; as a search query. Type &lt;code&gt;http://omnideck.omni&lt;/code&gt; once, or take the "did you mean" prompt, and it stops guessing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does mkcert stop browser warnings?
&lt;/h3&gt;

&lt;p&gt;mkcert creates a local certificate authority, registers it with your OS and browsers, and signs your certs with it. Browsers trust the CA, so every cert it issues is accepted with no per-site imports.&lt;/p&gt;

&lt;h3&gt;
  
  
  I rebooted and now I get ERR_CONNECTION_REFUSED
&lt;/h3&gt;

&lt;p&gt;Read that error as good news: DNS worked. The name resolved to 127.0.0.1 and nothing was listening there. A bad hosts entry gives you &lt;code&gt;ERR_NAME_NOT_RESOLVED&lt;/code&gt; instead, so this one is HAProxy, not DNS.&lt;/p&gt;

&lt;p&gt;On Linux, the usual culprit is the sysctl from Step 2. Without the permanent version, HAProxy comes back after the reboot, fails to bind 80 and 443, and exits. &lt;code&gt;brew services list&lt;/code&gt; and the log at &lt;code&gt;$(brew --prefix)/var/log/haproxy.log&lt;/code&gt; will confirm it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why HAProxy instead of nginx or Caddy?
&lt;/h3&gt;

&lt;p&gt;Any of them will do this. Caddy needs the least config and handles certs on its own. I reach for HAProxy because the ACL syntax stays readable as the rules pile up, the config validates before it loads, and it's the same thing I'd put in front of a real service. Use what you already know.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>localhost</category>
      <category>customtld</category>
      <category>omnideck</category>
    </item>
    <item>
      <title>The OpenAI API everyone copied isn't the one OpenAI recommends</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Wed, 24 Jun 2026 14:52:07 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/the-openai-api-everyone-copied-isnt-the-one-openai-recommends-28o8</link>
      <guid>https://dev.to/rlnorthcutt/the-openai-api-everyone-copied-isnt-the-one-openai-recommends-28o8</guid>
      <description>&lt;p&gt;I kept seeing "OpenAI-compatible" stamped on projects that have nothing to do with OpenAI. Ollama. vLLM. LM Studio. Most of the local model tools I run on my own hardware. None of them are OpenAI, yet they all advertise the same compatibility badge. So I went looking for what that badge actually means, and the answer turned out to be more interesting than I expected.&lt;/p&gt;

&lt;p&gt;There isn't one OpenAI API spec. There are two formats, they serve different purposes, and the one everyone copied is not the one OpenAI now tells you to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two formats, not one
&lt;/h2&gt;

&lt;p&gt;OpenAI exposes two main ways to send text and multimodal requests to its models. The older one is the &lt;strong&gt;Chat Completions API&lt;/strong&gt;. The newer one is the &lt;strong&gt;Responses API&lt;/strong&gt;, introduced in March 2025 and now recommended for new projects.&lt;/p&gt;

&lt;p&gt;This distinction matters because of a quiet mix-up I see all the time. When people say a tool is "OpenAI-compatible," they almost always mean Chat Completions. That's the format the rest of the industry cloned. The Responses API is the direction OpenAI is steering everyone toward, but it isn't the thing that became a standard. Knowing which one you're talking about saves a lot of confusion.&lt;/p&gt;

&lt;p&gt;Let me walk through both, then get to why this happened and whether you should care.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Chat Completions works
&lt;/h2&gt;

&lt;p&gt;Chat Completions models a request as a list of messages, where each message carries a &lt;code&gt;role&lt;/code&gt;. The roles do the work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;developer&lt;/code&gt; sets the persona and the rules. Older models called this &lt;code&gt;system&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user&lt;/code&gt; holds the human prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assistant&lt;/code&gt; holds the model's previous replies, which is how you replay conversation history.
A request looks like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.4-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a helpful assistant that speaks like a 1920s detective."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Where did I leave my keys?"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response wraps the answer inside a &lt;code&gt;choices&lt;/code&gt; array. The array exists because you can ask for more than one variation with the &lt;code&gt;n&lt;/code&gt; parameter, so even a single reply comes back at index zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chatcmpl-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1677652288&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.4-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Listen here, pal. If I knew where your brass keys were hiding, I'd be buying juice, not cracking wise."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"completion_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one catch worth knowing. Chat Completions is stateless, so the server doesn't remember your conversation. Every turn, you resend the full message history, including any tool outputs from earlier in the exchange. For a simple chatbot that's fine. For a multi-step agent that calls tools, it gets unwieldy fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Chat Completions became the standard
&lt;/h2&gt;

&lt;p&gt;OpenAI shipped this format in 2023, it worked, and it arrived early. That timing matters more than any technical merit.&lt;/p&gt;

&lt;p&gt;Once enough tutorials and production apps were written against the &lt;code&gt;messages&lt;/code&gt; and &lt;code&gt;choices&lt;/code&gt; shape, the format stopped belonging to OpenAI in any practical sense. Other model providers added compatibility so developers could swap them in without rewriting code. Local inference engines like vLLM and Ollama did the same. So did gateways like OpenRouter, whose whole job is normalizing access to many models behind one interface. At that point, supporting the format wasn't a favor to OpenAI. It was table stakes for anyone who wanted developers to adopt their thing.&lt;/p&gt;

&lt;p&gt;This is a classic network effect. The format won because it was everywhere, and it stayed everywhere because it won. Simon Willison &lt;a href="https://simonwillison.net/2025/Mar/11/responses-vs-chat-completions/" rel="noopener noreferrer"&gt;flagged the obvious risk back in 2025&lt;/a&gt;: a whole industry was building clones of one company's proprietary API, and that company could change it whenever it liked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Responses API differs
&lt;/h2&gt;

&lt;p&gt;So OpenAI changed it. Sort of.&lt;/p&gt;

&lt;p&gt;The Responses API is a redesign aimed at agents rather than chatbots. It separates your standing instructions from the actual input, and it can hold conversation state on the server so you stop resending the whole transcript.&lt;/p&gt;

&lt;p&gt;A request is leaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"instructions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are a concise data analyst."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize our Q2 performance."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;instructions&lt;/code&gt; field carries the system-level guidance. You pass the actual prompt through &lt;code&gt;input&lt;/code&gt;, either as a plain string or a list of messages. If you want the server to remember the last turn, you send &lt;code&gt;store: true&lt;/code&gt; and then pass a &lt;code&gt;previous_response_id&lt;/code&gt; on the next call instead of replaying everything.&lt;/p&gt;

&lt;p&gt;The response is where I see the most misinformation, so here's the real shape. The output is not a single top-level string. It's a typed &lt;code&gt;output&lt;/code&gt; array of items, because a single response can now contain a message, a tool call, a reasoning trace, and more, all on one timeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resp_9z8x7c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1782384000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"msg_..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"output_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Q2 revenue rose 14% quarter over quarter, driven mostly by enterprise software renewals."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"annotations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;44&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see &lt;code&gt;response.output_text&lt;/code&gt; in a lot of examples, and it looks like a top-level field. It isn't. It's a convenience helper in the SDK that digs into the &lt;code&gt;output&lt;/code&gt; array and pulls out the text for you. Handy, but don't expect it in the raw JSON. Also note the token counts renamed themselves: Chat Completions reports &lt;code&gt;prompt_tokens&lt;/code&gt; and &lt;code&gt;completion_tokens&lt;/code&gt;, while Responses uses &lt;code&gt;input_tokens&lt;/code&gt; and &lt;code&gt;output_tokens&lt;/code&gt;. Small thing, easy to trip on.&lt;/p&gt;

&lt;p&gt;OpenAI's pitch for Responses is concrete. Better cache utilization cuts cost on multi-turn workloads. Reasoning models score higher because the API preserves their reasoning context between turns. Built-in tools like web search and code execution save you from wiring up your own function-calling loop. For agent work, those are real wins.&lt;/p&gt;

&lt;p&gt;There's a deeper reason the stateful design matters, and it's easy to miss. Reasoning models generate a hidden chain of thought before they answer. In a stateless setup, the client has to send the whole history back every turn. That forces an awkward choice. You either strip the reasoning out and lose the model's train of thought, or ship it back and forth as encrypted blocks. Holding state on the server avoids both. OpenAI keeps the reasoning trace on its own backend from one turn to the next, so the model stays sharp without exposing how it got there. That's one of the real reasons behind the push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Responses changes the question
&lt;/h2&gt;

&lt;p&gt;Here's the part that made this worth a blog post. In January 2026, OpenAI and a group of partners published &lt;a href="https://www.openresponses.org/" rel="noopener noreferrer"&gt;Open Responses&lt;/a&gt;, an open-source specification built on the Responses API.&lt;/p&gt;

&lt;p&gt;The launch partners are telling. Hugging Face, Vercel, OpenRouter, LM Studio, Ollama, and vLLM all signed on. These are the same tools that cloned Chat Completions on their own. This time, instead of reverse-engineering a proprietary format and hoping it doesn't shift, they helped write a documented spec with formal acceptance tests and a shared schema. The vLLM team &lt;a href="https://thenewstack.io/open-responses-vs-chat-completion-a-new-era-for-ai-apps/" rel="noopener noreferrer"&gt;said as much&lt;/a&gt;: they used to guess at provider behavior, and a real spec ends that.&lt;/p&gt;

&lt;p&gt;The idea is one schema you describe requests and outputs against once, then run across OpenAI, local models, or other providers with minimal translation. Notably absent from the launch lineup were Anthropic and Google DeepMind, which keep their own formats. Even so, the spec lists both as targets it aims to reach through adapter layers, so the plan is to cover them, not route around them. This isn't a universal peace treaty, but it's the open part of the field agreeing on a baseline.&lt;/p&gt;

&lt;p&gt;There's an irony nobody is hiding. Building an "open" standard on top of one company's API is a strange way to escape that company's gravity. But for anyone tired of writing a wrapper around a wrapper, a documented, testable spec beats a de facto one that lives at OpenAI's discretion.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few parameters worth knowing
&lt;/h2&gt;

&lt;p&gt;Whichever format you call, a handful of payload options control behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;temperature&lt;/code&gt; sets randomness. Push it to 0.9 for creative output, drop it to 0.2 for focused, near-deterministic answers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stream&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt; returns text token by token over Server-Sent Events instead of making you wait for the full reply. The streaming events differ between the two formats, which is one more reason adapters need a spec.&lt;/li&gt;
&lt;li&gt;Structured output is handled differently in each. Chat Completions uses &lt;code&gt;response_format&lt;/code&gt; with a JSON schema. Responses uses &lt;code&gt;text.format&lt;/code&gt;. Both can force valid JSON out of the model.&lt;/li&gt;
&lt;li&gt;For length limits, watch the naming. Chat Completions deprecated &lt;code&gt;max_tokens&lt;/code&gt; in favor of &lt;code&gt;max_completion_tokens&lt;/code&gt;. Responses uses &lt;code&gt;max_output_tokens&lt;/code&gt;. Same intent, three different names, depending on where you are.
## Where I landed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical takeaway, especially if you self-host like I do, is that compatibility is the whole game. Because Ollama and vLLM speak the OpenAI formats, I can point a tool at a local model and a frontier model with the same code and only a base URL between them. That portability is worth real money and real freedom, and it's exactly what Open Responses is trying to protect going forward.&lt;/p&gt;

&lt;p&gt;If you're starting something new, build against the Responses API and lean on the Open Responses spec where you can. If you're maintaining an existing app, Chat Completions isn't going anywhere. OpenAI has committed to supporting it indefinitely, and the rest of the field still runs on it. Either way, knowing which spec you're actually talking about is half the battle.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading: OpenAI's &lt;a href="https://developers.openai.com/api/docs/guides/migrate-to-responses" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;, the &lt;a href="https://www.openresponses.org/specification" rel="noopener noreferrer"&gt;Open Responses specification&lt;/a&gt;, and Simon Willison's &lt;a href="https://simonwillison.net/2025/Mar/11/responses-vs-chat-completions/" rel="noopener noreferrer"&gt;original take&lt;/a&gt; on the standardization risk.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@zmachacek?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Zdeněk Macháček&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/two-black-and-yellow-bird-perching-on-tree-branch-EtxsgEcHnZg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>When the conclusion comes first</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Sun, 14 Jun 2026 19:40:48 +0000</pubDate>
      <link>https://dev.to/rlnorthcutt/when-the-conclusion-comes-first-478o</link>
      <guid>https://dev.to/rlnorthcutt/when-the-conclusion-comes-first-478o</guid>
      <description>&lt;p&gt;&lt;em&gt;A sponsored benchmark is just an experiment with a sponsor. So read it like one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I read a lot of vendor benchmarks. Most are fine. They are marketing, sure, but the honest ones tell you what they tested, show their work, and let you decide if it maps to your world.&lt;/p&gt;

&lt;p&gt;Then there are the other ones. The ones where you can feel the conclusion was written before the test was. You read the setup and realize the whole thing was arranged to arrive at a number that was decided in a meeting months earlier.&lt;/p&gt;

&lt;p&gt;A new one landed recently (March 2026) that is a near-perfect teaching example, so I want to walk through it. Not to dunk on a product, but because the techniques in it show up everywhere, and once you can spot them you can spot them in any report.&lt;/p&gt;

&lt;p&gt;The report is Tolly #226104, published in March 2026 and commissioned by F5. It compares F5's BIG-IP Next for Kubernetes running on an NVIDIA Bluefield DPU against three open source load balancers: HAProxy, Envoy, and a third "other open-source solution" that, oddly, never gets named. The pitch is that F5's intelligent AI load balancing crushes the open source field on token throughput, time to first token, and CPU usage in an AI inference cluster.&lt;/p&gt;

&lt;p&gt;I want to be fair up front about two things, because they matter.&lt;/p&gt;

&lt;p&gt;First, I have no reason to think the numbers are faked. I think the data was measured and reported honestly. That is not where these reports usually go wrong.&lt;/p&gt;

&lt;p&gt;Second, the underlying ideas are real. Offloading network work to a DPU genuinely frees up host CPU. Routing requests around busy GPUs is genuinely smarter than routing blindly. Both of those are good engineering. If the report had simply said "our purpose-built, GPU-aware product on dedicated offload hardware beats a general-purpose proxy that was told to ignore GPU load," it would be true. It would also be boring and obvious, which is exactly why the report does not say that.&lt;/p&gt;

&lt;p&gt;The problem is not the data. The problem is the experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the test was actually built to measure
&lt;/h2&gt;

&lt;p&gt;Here is the core of the setup. Before each run, the testers manually loaded 50% of the GPUs with background traffic. That load did not pass through any of the load balancers. It was just dumped onto half the cluster to create a lopsided, half-congested pool.&lt;/p&gt;

&lt;p&gt;Then they sent real traffic through each load balancer and measured the result.&lt;/p&gt;

&lt;p&gt;F5's product is designed to watch GPU load and steer traffic toward the idle accelerators. The open source proxies were configured with plain round-robin, which sprays requests evenly without caring whether a GPU is already buried.&lt;/p&gt;

&lt;p&gt;So the test does exactly one thing: it checks whether a load balancer can detect and avoid pre-loaded GPUs. F5 had that feature switched on. Everyone else had it switched off. The result was decided the moment they chose the configurations.&lt;/p&gt;

&lt;p&gt;This is the part that should bother you, because round-robin is a strawman. No competent engineer runs static round-robin into a pool where half the backends are known to be slammed. HAProxy, Envoy, and the rest all support dynamic, load-aware algorithms and health-based routing that exist precisely for uneven backends. None of that was turned on. The open source tools were handed the dumbest possible config and then measured at the one task that config is guaranteed to fail.&lt;/p&gt;

&lt;p&gt;Would smarter configs have closed the gap completely? I honestly do not know, and I am not going to pretend otherwise. Connection-aware routing is not the same thing as GPU-load-aware routing, and F5's product was built specifically for this scenario. The fair comparison, with every tool tuned by someone who actually wanted it to do well, was never run. That is the whole point. The report does not answer the interesting question. It answers a rigged one and prints the rigged answer in 40-point font.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CPU number is a hardware fact dressed as a software win
&lt;/h2&gt;

&lt;p&gt;The other headline is that F5 used about 2 CPU cores while HAProxy used about 12, out of 16 available. Roughly 80% less CPU.&lt;/p&gt;

&lt;p&gt;That gap is real, and it is also not surprising, because F5 was running offloaded to a DPU with its own dedicated ARM cores and HAProxy was running on the host. Of course the host uses less CPU when you move the work to a separate chip. That is what offload hardware is for. The honest version of this claim is "dedicated offload silicon uses fewer host cores than host software," which is true of basically any offload, for any workload, forever.&lt;/p&gt;

&lt;p&gt;Attributing that to software efficiency is like winning a footrace because you brought a motorcycle, then writing up your superior running form.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read any report like this
&lt;/h2&gt;

&lt;p&gt;Strip away the specifics and you are looking at an experiment. So judge it the way you would judge any experiment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Follow the money.&lt;/strong&gt; Who paid for it, and does the result flatter them? F5 commissioned this. That alone is not damning, plenty of good research is funded, but it raises the bar for everything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the changed variables.&lt;/strong&gt; A clean experiment changes one thing. This one changed the hardware platform, the routing algorithm, and the software maturity all at once, then credited the winner's software. When several variables move together, you cannot attribute the outcome to any single one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the baseline.&lt;/strong&gt; Was the thing being beaten given a fair shot, or set up to lose? Round-robin into a half-loaded pool is a baseline chosen to fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for cherry-picking.&lt;/strong&gt; The "up to 40 / 61 / 34%" headline quietly picks the worst competitor for each separate metric. Against Envoy, the throughput edge was 21% and the latency edge 17%. And the most jaw-dropping figures, 114% and 406%, came from smaller models that were only ever tested against HAProxy, "due to time constraints."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try to reproduce it.&lt;/strong&gt; Could you? An unnamed third competitor, early-access software, and no published configs mean no, you cannot. Real results survive other people repeating them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tolly, to their credit, says the quiet part in the fine print. Their own terms note that tests "may have been tailored to reflect performance under ideal conditions." That sentence is doing a lot of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually disappoints me
&lt;/h2&gt;

&lt;p&gt;None of this is illegal, and Tolly is a long-standing testing house that documented its setup well enough that a careful reader can take it apart. I would rather have this report, with its methodology on the page, than a glossy claim with no test behind it at all.&lt;/p&gt;

&lt;p&gt;What gets me is that F5 did not need to do this. They are an enormous company with real engineering and a product that, by their own description, has a genuine architectural idea behind it. They could have published a transparent, reproducible, tuned-on-both-sides comparison and let it stand on its merits. Instead they paid for a setup engineered to make the entire open source proxy ecosystem look primitive at a task the test was built to make it fail.&lt;/p&gt;

&lt;p&gt;That is the move I find hard to respect. Not competing. Competing is great. It is using scale and a budget to manufacture a lopsided story about volunteer-built and community-built software that cannot commission a rebuttal report of its own.&lt;/p&gt;

&lt;p&gt;So read the report. Seriously, the link is public at tolly.com/publications/226104. Read the methodology, not the bottom line. Then ask who paid, what changed, and whether you could ever run it yourself. If the answers do not hold up, the big number on the front page does not either.&lt;/p&gt;

&lt;p&gt;Whether F5's product is actually better is a real and open question. This report just is not the thing that answers it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@loomydoons?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Myles Bloomfield&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-sculpture-with-balls-on-top-of-it-p8JbzOUwdjg?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>loadbalancer</category>
      <category>aiops</category>
    </item>
    <item>
      <title>Variables in Javascript: A Comprehensive Guide to Var, Let, and Const</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Mon, 22 Jan 2024 08:00:00 +0000</pubDate>
      <link>https://dev.to/appsmith/variables-in-javascript-a-comprehensive-guide-to-var-let-and-const-4n86</link>
      <guid>https://dev.to/appsmith/variables-in-javascript-a-comprehensive-guide-to-var-let-and-const-4n86</guid>
      <description>&lt;p&gt;In software, a variable is a symbolic name that represents a &lt;em&gt;value&lt;/em&gt; or a &lt;em&gt;reference to a value&lt;/em&gt;. It's a way to store information that can be reused and manipulated throughout your program. You can think of a variable like a label or even a sign pointer - it represents the data (which can potentially change) but is not the actual data itself.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;I am often reminded of that great scene in the Bruce Lee movie, "Enter the Dragon", where he tells his student that "it is like a finger pointing the way to the moon - don't concentrate on the finger, or you will miss all of that heavenly glory." The variable is important, but what it represents is actually what you care about.&lt;br&gt;
  &lt;iframe src="https://www.youtube.com/embed/4O9o4CKTGzQ?start=74"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Ok, maybe that's a little bit of a stretch, but you get the idea. Variables are essential building blocks in JavaScript programming, allowing developers to store and manipulate data. There are 3 main ways to declare a variable, and your choice can have significant implications on your code's behavior and maintainability. Many developers just choose one and use it for everything, but that's just lazy and can sometimes cause problems.&lt;/p&gt;

&lt;p&gt;Let's explore these three methods of variable declaration in JavaScript: &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, and &lt;code&gt;const&lt;/code&gt;, diving into when and why to use each, along with their respective advantages and drawbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;var&lt;/code&gt;: Function-Scoped Variables
&lt;/h2&gt;

&lt;p&gt;In JavaScript, &lt;code&gt;var&lt;/code&gt; is a keyword used to declare a variable. It has some specific behaviors that set it apart from other variable declaration keywords like &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's what you need to know about &lt;code&gt;var&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Function Scope&lt;/strong&gt;: Variables declared with &lt;code&gt;var&lt;/code&gt; are function-scoped, meaning they are accessible within the function where they were declared (or globally if declared outside any function).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hoisting&lt;/strong&gt;: Unlike &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;, variables declared with &lt;code&gt;var&lt;/code&gt; are "hoisted" to the top of their containing function or global scope. This means that they are technically available from the beginning of that scope, but their value will be &lt;code&gt;undefined&lt;/code&gt; until the code where they are assigned is executed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Reassignment&lt;/strong&gt;: You can reassign new values to a variable declared with &lt;code&gt;var&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Redeclaration&lt;/strong&gt;: In non-strict mode, you can redeclare a variable using &lt;code&gt;var&lt;/code&gt; in the same scope without getting an error, which is not allowed with &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Block Scope&lt;/strong&gt;: &lt;code&gt;var&lt;/code&gt; does not respect block scope (such as inside an &lt;code&gt;if&lt;/code&gt; statement or a loop), which can sometimes lead to unexpected behavior. If you declare a variable with &lt;code&gt;var&lt;/code&gt; inside a block, it's actually available to the entire surrounding function or global scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because of these peculiarities, and with the introduction of &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt; in ES6 (ECMAScript 2015), the use of &lt;code&gt;var&lt;/code&gt; has become less common in modern JavaScript, and it's often recommended to use &lt;code&gt;let&lt;/code&gt; or &lt;code&gt;const&lt;/code&gt; instead for clearer scoping rules and better maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  You need a variable with function-level scope.&lt;/li&gt;
&lt;li&gt;  You are working with older code that doesn't support ES6.&lt;/li&gt;
&lt;li&gt;  Typically, you should avoid &lt;code&gt;var&lt;/code&gt;, but it can be handy in some cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Function Scope:&lt;/strong&gt; Variables are accessible within the entire function where they're declared.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hoisting:&lt;/strong&gt; Variables are moved to the top of their scope and initialized with &lt;code&gt;undefined&lt;/code&gt;, allowing them to be referenced before declaration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Lack of Block Scope:&lt;/strong&gt; Variables can be accessed outside the block they were declared in, leading to potential bugs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hoisting Quirks:&lt;/strong&gt; Can cause confusion as variables are available before their declaration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;varExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// undefined, because of hoisting&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 10&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Same variable, even though it's in a different block&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 20, because var does not have block scope&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. &lt;code&gt;let&lt;/code&gt;: Block-Scoped Variables
&lt;/h2&gt;

&lt;p&gt;So, if we had &lt;code&gt;var&lt;/code&gt;, why did we need something else? The reason why the &lt;code&gt;let&lt;/code&gt; keyword was introduced to javascript was because &lt;em&gt;function&lt;/em&gt; scope is confusing and this led to a number of bugs and errors. &lt;code&gt;let&lt;/code&gt; was was introduced in ES6 (ECMAScript 2015) as an alternative to var, with some key differences in behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Block Scope&lt;/strong&gt;: Unlike var, variables declared with let are block-scoped, meaning they are only accessible within the block in which they were declared (e.g., inside an if statement or a loop).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Hoisting&lt;/strong&gt;: Although let declarations are hoisted, the variables are not initialized until the code execution reaches the declaration. Attempting to access the variable before its declaration will result in a ReferenceError.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Reassignment&lt;/strong&gt;: Like var, you can reassign new values to a variable declared with let.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Redeclaration&lt;/strong&gt;: In strict mode, you cannot redeclare a variable using let in the same scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  You need a variable with block-level scope.&lt;/li&gt;
&lt;li&gt;  You expect to reassign the variable within its scope.&lt;/li&gt;
&lt;li&gt;  Probably should be your default method for creating variables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Block Scope:&lt;/strong&gt; Variables are only accessible within the block where they're declared, reducing potential errors.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;No Hoisting Issues:&lt;/strong&gt; Variables are not initialized until the code execution reaches the declaration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;More Limited Scope:&lt;/strong&gt; Might require more careful planning of where the variable is declared.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;letExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError, because it doesn't exist yet (no hoisting)&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Different variable because it's in a different block&lt;/span&gt;
            &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I see how it works&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 10, because we are in the block for the original variable&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError, because y is block-scoped&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. &lt;code&gt;const&lt;/code&gt;: Block-Scoped Immutable References
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;const&lt;/code&gt; keyword, which, like &lt;code&gt;let&lt;/code&gt;, was introduced in ES6 (ECMAScript 2015). It has similarities to &lt;code&gt;let&lt;/code&gt;, but with some unique characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Block Scope&lt;/strong&gt;: Just like &lt;code&gt;let&lt;/code&gt;, variables declared with &lt;code&gt;const&lt;/code&gt; are block-scoped.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Hoisting&lt;/strong&gt;: Similar to &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt; declarations are hoisted, but accessing them before their declaration in the code results in a ReferenceError.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Reassignment&lt;/strong&gt;: Unlike &lt;code&gt;var&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt;, once a variable is assigned with &lt;code&gt;const&lt;/code&gt;, it cannot be reassigned. Attempting to do so will result in a TypeError.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;No Redeclaration&lt;/strong&gt;: You cannot redeclare a variable using &lt;code&gt;const&lt;/code&gt; in the same scope.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Must Be Initialized&lt;/strong&gt;: A &lt;code&gt;const&lt;/code&gt; declaration must be initialized with a value at the time it's declared.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When to Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  You want to declare a variable that should not be reassigned.&lt;/li&gt;
&lt;li&gt;  You need block-level scoping.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Immutable Reference:&lt;/strong&gt; Prevents reassignment, making the code more predictable.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Block Scope:&lt;/strong&gt; Like &lt;code&gt;let&lt;/code&gt;, variables are only accessible within the block where they're declared.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Not Fully Immutable:&lt;/strong&gt; Only the reference is constant, not the object itself. If the variable is an object, its properties can still be altered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;constExample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError, because it doesn't exist yet (no hoisting)&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.14159&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Error: Assignment to constant variable&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError, because PI is block-scoped&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This is fine because object properties can be changed&lt;/span&gt;
        &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// TypeError, reassignment not allowed&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In modern JavaScript development, understanding the appropriate use of &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, and &lt;code&gt;const&lt;/code&gt; is crucial. While you can safely plan to use &lt;code&gt;let&lt;/code&gt; most of the time, it is important to understand how block scope works so you know when it makes sense. &amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Use &lt;code&gt;var&lt;/code&gt; for function-level scope, mainly when dealing with &lt;strong&gt;older code&lt;/strong&gt; or specific scoping needs.&lt;/li&gt;
&lt;li&gt;  Utilize &lt;code&gt;let&lt;/code&gt; when block-level scope is required, and you &lt;strong&gt;expect the value to change within the block&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Opt for &lt;code&gt;const&lt;/code&gt; when you want to ensure that the reference to a &lt;strong&gt;value stays constant within a block scope&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By carefully choosing the right declaration method, developers can write code that is not only more readable but also more resilient and less prone to errors and unexpected behavior. It is usually better to hit scope-related errors earlier (more strict) than later (less strict).&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>variables</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Hands-on Guide to Git Rebase &amp; Resolving Conflicts</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Fri, 08 Dec 2023 07:00:00 +0000</pubDate>
      <link>https://dev.to/appsmith/a-hands-on-guide-to-git-rebase-resolving-conflicts-59d5</link>
      <guid>https://dev.to/appsmith/a-hands-on-guide-to-git-rebase-resolving-conflicts-59d5</guid>
      <description>&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you will have a practical understanding of how to use git rebase to integrate changes from one branch to another and how to maintain a clean commit history.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want a more information about this image? Checkout the "&lt;a href="https://community.appsmith.com/content/guide/understanding-git-rebase" rel="noopener noreferrer"&gt;Understanding Git Rebase&lt;/a&gt;" guide.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Basic understanding of Git&lt;/li&gt;
&lt;li&gt;Git installed on your system&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Git rebase&lt;/code&gt; is a powerful command that allows you to integrate changes from one branch into another. Instead of merging, which takes all the changes in one branch and merges them into another with a new &lt;code&gt;merge&lt;/code&gt; commit, &lt;code&gt;rebase&lt;/code&gt; takes a different approach. It replays your changes on top of the branch you're rebasing onto. This results in a cleaner, more linear commit history.&lt;/p&gt;

&lt;p&gt;Of course, it's not all sunshine and rainbows - problems do pop up. So, lets take a look at how to use &lt;code&gt;git rebase&lt;/code&gt; and also how to deal with conflicts when they arise.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmkfccc2uwpxa9cm3qzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmkfccc2uwpxa9cm3qzr.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Set up a Test Repository
&lt;/h3&gt;

&lt;p&gt;Lets go ahead and create a test repository that we can use to play around with &lt;code&gt;git rebase&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize a new repository
git init rebase-demo
cd rebase-demo

# Create a main branch for primary development
git checkout -b main

# Create a file and commit it to the main branch
echo "Initial content" &amp;gt; file.txt
git add file.txt
git commit -m "Initial commit on main"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create a New Feature Branch
&lt;/h3&gt;

&lt;p&gt;Now, let's create a new feature branch that we will &lt;code&gt;rebase&lt;/code&gt; into the main branch later. We will create a simple file and commit that change to the branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a feature branch and add a file to it.
git checkout -b feature-branch
echo "Some feature content" &amp;gt;&amp;gt; file.txt
git commit -am "Add feature content"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Make More Changes to the Main Branch
&lt;/h3&gt;

&lt;p&gt;Let's go back to the main branch and make some more changes. These changes are what our &lt;code&gt;rebase&lt;/code&gt; will be added to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Go back to the main branch and make changes
git checkout main
echo "Another line in main" &amp;gt;&amp;gt; file.txt
git commit -am "Update in main"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you have two branches: &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;feature-branch&lt;/code&gt;, both with unique commits.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14hwmfasg34f01xf5t63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14hwmfasg34f01xf5t63.png" alt=" " width="769" height="760"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Rebase the Feature Branch
&lt;/h3&gt;

&lt;p&gt;Now, the magic happens. We switch to the feature branch, and &lt;code&gt;rebase&lt;/code&gt; it &lt;em&gt;into&lt;/em&gt; main.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout feature-branch
git rebase master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, is that it? Is it really that simple?&lt;/p&gt;

&lt;p&gt;It can be, for sure! Assuming all went well, the commit history will show the &lt;code&gt;feature-branch&lt;/code&gt; commits will be on the main branch. Those experienced or very observant developers may have noticed a problem above... we will have a conflict. However, not all is lost. Let's take a look at how you can adapt to this situation and fix any minor problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Resolve Conflicts
&lt;/h3&gt;

&lt;p&gt;During the rebase, you'll encounter a conflict because line 2 of &lt;code&gt;file.txt&lt;/code&gt; is different in each branch. That's ok - no need to panic. All merge conflicts can be fixed with the same basic steps. Here's how to resolve it: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identifying the Conflict&lt;/strong&gt;: Git will indicate that there's a conflict. You'll see a message similar to:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
error: could not apply a669cc6... Add feature content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This message tells you that there's a conflict in &lt;code&gt;file.txt&lt;/code&gt; that needs resolution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Evaluating the status&lt;/strong&gt;: One of the great things about Git is that you can always ask for a status update to see what is going on and what you may need to work on. Just make a note of the problems to fix and handle them one at a time.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interactive rebase in progress; onto 117fde3
Last command done (1 command done):
    pick a669cc6 Add feature content
No commands remaining.
You are currently rebasing branch 'feature-branch' on '117fde3'.
    (fix conflicts and then run "git rebase --continue")
    (use "git rebase --skip" to skip this patch)
    (use "git rebase --abort" to check out the original branch)
Unmerged paths:
    (use "git restore --staged &amp;lt;file&amp;gt;..." to unstage)
    (use "git add &amp;lt;file&amp;gt;..." to mark resolution)
    both modified:   file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Viewing the Conflict&lt;/strong&gt;: Depending on how many changes you are dealing with, you may know exactly what the problem is. Alternatively, you can also use &lt;code&gt;git diff&lt;/code&gt; to see the changes. Open &lt;code&gt;file.txt&lt;/code&gt; in your favorite editor. You'll see the conflicting changes demarcated like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial content
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
Another line in master
=======
Some feature content
 &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; Add feature content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content between &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD&lt;/code&gt; and &lt;code&gt;=======&lt;/code&gt; is from the &lt;code&gt;main&lt;/code&gt; branch, and the content between &lt;code&gt;=======&lt;/code&gt; and &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; Add feature content&lt;/code&gt; is from the &lt;code&gt;feature-branch&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resolving the Conflict&lt;/strong&gt;: Edit &lt;code&gt;file.txt&lt;/code&gt; to keep the content you want. For instance, if you want to keep the content from both branches but want the feature content to appear before the master content, you'd modify &lt;code&gt;file.txt&lt;/code&gt; to look like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial content
Some feature content
Another line in master
Save and close the file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Marking the Conflict as Resolved&lt;/strong&gt;: After resolving the conflict, mark it as resolved with Git:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuing the Rebase&lt;/strong&gt;: Once the conflict is resolved and marked as such, continue the &lt;code&gt;rebase&lt;/code&gt; process:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git rebase --continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are more conflicts, Git will pause again and let you resolve them. If there are no more conflicts, the &lt;code&gt;rebase&lt;/code&gt; will be completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Check the Commit History:
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;git log&lt;/code&gt; to see a clean, linear history of commits, showing that the feature branch commits now come after the master branch commits. Once the &lt;code&gt;rebase&lt;/code&gt; is successful, it will be a fast-forward merge, maintaining a linear history.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffso2js9ctdkajc747wx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffso2js9ctdkajc747wx8.png" alt=" " width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Git rebase&lt;/code&gt; is a powerful tool for maintaining a clean commit history and integrating changes from one branch into another. While it can be a bit more complex than merging, especially when conflicts arise, the resulting linear history can be worth the extra effort, especially in large projects.&lt;/p&gt;

&lt;p&gt;Remember, practice makes perfect. Feel free to experiment with git in safe environments (like our demo repo) before applying it to larger projects. Happy rebasing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-rebase" rel="noopener noreferrer"&gt;Git Rebase Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.appsmith.com/content/guide/understanding-git-rebase" rel="noopener noreferrer"&gt;Understanding Git Rebase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>rebase</category>
      <category>tutorial</category>
      <category>developer</category>
    </item>
    <item>
      <title>How to Correctly Execute a Loop Synchronously with Javascript</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Wed, 11 Oct 2023 08:37:59 +0000</pubDate>
      <link>https://dev.to/appsmith/how-to-correctly-execute-a-loop-synchronously-with-javascript-4gkm</link>
      <guid>https://dev.to/appsmith/how-to-correctly-execute-a-loop-synchronously-with-javascript-4gkm</guid>
      <description>&lt;p&gt;Understanding synchronous and asynchronous operations is fundamental when working with JavaScript, especially when dealing with loops. In this article, we will explore how to run loops synchronously, even when they contain asynchronous tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Synchronous for Loop
&lt;/h2&gt;

&lt;p&gt;To execute a loop synchronously with JavaScript, you can use the for loop. The for loop allows you to iterate over a sequence of values and execute a block of code for each value. By default, the for loop is synchronous, meaning that each iteration will wait for the previous iteration to complete before starting.&lt;/p&gt;

&lt;p&gt;Here's an example of a for loop that counts from 1 to 10 synchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the loop starts with i equal to 1 and increments i by 1 each time through the loop until i is equal to 10. The console.log() statement inside the loop will be executed for each value of i, printing the value to the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Asynchronous Operations
&lt;/h2&gt;

&lt;p&gt;However, real-world scenarios might require you to perform asynchronous tasks within your loop, like fetching data from a server. Here's where understanding the synchronous nature of loops becomes essential.&lt;/p&gt;

&lt;p&gt;For instance, if you used JavaScript's native fetch method or another API call within a loop, it would not work synchronously by default. Each request would be initiated almost simultaneously, and the responses would likely return out of order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Loops Synchronous with Asynchronous Tasks
&lt;/h2&gt;

&lt;p&gt;If you need to execute an asynchronous task inside the loop, such as making an API call or fetching data, you can use the async/await syntax to wait for the task to complete before moving on to the next iteration of the loop. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="c1"&gt;// Simulating an API call&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Data for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the fetchData() function makes an asynchronous API call and returns the result. Inside the myFunction() loop, we use the await keyword to wait for the API call to complete before moving on to the next loop iteration. This ensures that the loop is executed synchronously, with each iteration waiting for the previous iteration to complete before starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand your needs
&lt;/h2&gt;

&lt;p&gt;While using async/await within loops guarantees order, it might not be the most efficient approach for all use cases, especially if the asynchronous operations don't depend on the result of the previous one. In such cases, running them concurrently might be more time-efficient. You might also want to use events or even Promise.all() to handle all the results when they are complete.&lt;/p&gt;

&lt;p&gt;Using loops synchronously in JavaScript is straightforward. However, when introducing asynchronous operations within loops, it's essential to ensure they maintain the desired flow. The async/await syntax offers a neat way to achieve this, but it's vital to consider the efficiency and nature of the tasks when applying this method.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Evolution of Git: A Dive Into Tech History</title>
      <dc:creator>Ron Northcutt</dc:creator>
      <pubDate>Tue, 19 Sep 2023 16:49:19 +0000</pubDate>
      <link>https://dev.to/appsmith/the-evolution-of-git-a-dive-into-tech-history-2j52</link>
      <guid>https://dev.to/appsmith/the-evolution-of-git-a-dive-into-tech-history-2j52</guid>
      <description>&lt;p&gt;In the earliest days of my career, I never used version control. At that time, Git didn't exist and most version control systems were clunky tools used mostly by corporate engineering teams. I was doing a lot of freelance consulting, so most of my projects were just me working alone. SFTP, occasional backups, and lots of &lt;code&gt;*.bak&lt;/code&gt; files worked reasonably well. Still, I lost work or wished I could return to an earlier version after multiple refactors... especially since I was still learning.&lt;/p&gt;

&lt;p&gt;When I joined my first team as a developer, I was introduced to "Subversion". This was a whole new world - the ability to see all changes, go back in time, and to safely make changes was great. It changed the way I thought about code. The problem is that it was slooooow. I was working on a large project, and updates took quite a while.&lt;/p&gt;

&lt;p&gt;Then, I joined a company as a Sr. Developer, and they were using this new thing called "Git". I wasn't familiar with it, and actually, my first commit to the main branch broke the app in production &lt;em&gt;(cue heart attack)&lt;/em&gt;! I had no idea about merge conflicts or how to fix them. However, once I got the basics down, I became a huge fan. It was FAST. So much faster than Subversion, and really much easier to use. With just a few commands, I was fairly proficient, and for more complex things I could easily find help online.&lt;/p&gt;

&lt;p&gt;Today, it is almost impossible to learn about software development without learning about Git. For many developers, the command &lt;code&gt;git commit&lt;/code&gt; is a daily ritual. It's the heartbeat of countless projects yet, how many of us know the story behind this powerful tool? We've all heard bits and pieces, but let's take a deeper look at Git and how it came into being.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Git?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm a very lazy person who likes to get credit for things other people actually do. In the Linux project, I started it, and I'm still considered the central point, but I'm not a good programmer. I'm a good manager. My only job is to say 'no' to people, and sometimes I accept patches and put them into my tree. I wanted a tool where I can do that. That was my only design criteria, and it turns out when you do that kind of tool, you can use it for a lot of other things too."&lt;/p&gt;

&lt;p&gt;-- Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To start with, what the heck is Git? At its core, Git is a distributed version control system (VCS). It lets multiple users track changes in source code during software development, maintaining a history of code changes, and ensuring traceability. It is like a &lt;strong&gt;shared digital diary&lt;/strong&gt; for computer code, allowing many people to write in it while keeping a record of all changes made over time.&lt;/p&gt;

&lt;p&gt;But unlike many version control systems that came before it, Git operates on a distributed model, meaning every developer's working copy of the code is also a repository with complete history and version tracking abilities, independent of network access. This means that developers can work locally on their machine, and then sync those changes later. This was a big change in how VCS affects the daily workflow of a developer.&lt;/p&gt;

&lt;p&gt;One of Git's key advantages is its efficiency in handling changes. Instead of sending an entire copy of the codebase each time, Git only transmits the differences (or changes) from the last known version. This approach significantly reduces the amount of data transmitted and results in faster sync times, providing a performance advantage, especially for large projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Genesis: Why Was Git Created?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"So I’d like to stress that while it really came together in just about ten days or so (at which point I did my first *kernel* commit using git), it wasn’t like it was some kind of mad dash of coding. The actual amount of that early code is actually fairly small, it all depended on getting the basic ideas right. And that I had been mulling over for a while before the whole project started. I’d seen the problems others had. I’d seen what I wanted to avoid doing."&lt;/p&gt;

&lt;p&gt;-- Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flashback to 2005. The Linux kernel, an enormous open-source project, used a proprietary VCS called BitKeeper. However, due to a conflict between the community and the company behind BitKeeper, the free-of-charge status was revoked. Necessity is the mother of invention, and this incident paved the way for the creation of a new system. Linus Torvalds, the creator of the Linux kernel, took the reins. He aimed to create a tool that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Distributed:&lt;/strong&gt; Unlike other systems where a central repository was required.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Compatible:&lt;/strong&gt; The new tool would incorporate as many features and workflows as BitKeeper.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Secure:&lt;/strong&gt; Ensure the integrity of source code and protect against corruption, accidental or malicious.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, after a few days of work, Torvalds had a working prototype of Git, and less than a month later, Git was managing the Linux kernel source code. On April 7, 2005, Linus Torvalds made the &lt;a href="https://github.com/git/git/commit/e83c5163316f89bfbde7d9ab23ca2e25604af290" rel="noopener noreferrer"&gt;first-ever commit to Git&lt;/a&gt;. It wasn’t a grand feature; it was a simple README file. However, the major version (v1.0) took its time and was released on December 21, 2005. But, what's interesting is that version 2.0 was released almost a &lt;em&gt;decade&lt;/em&gt; later on June 1, 2014.&lt;/p&gt;

&lt;p&gt;Git got the important things right in the beginning, and that set the stage for a valuable project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Showdown: Git vs. CVS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;'Well, it was obviously designed for our workflow, so that is part of it. I’ve already mentioned the whole “distributed” part many times, but it bears repeating. But it was also designed to be efficient enough for a biggish project like Linux, and it was designed to do things that people considered “hard” before git – because those are the things *I* do every day.'&lt;/p&gt;

&lt;p&gt;-- Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concurrent Versions System (CVS) was one of the pioneering tools in the version control domain. So how does Git stand out when compared to such a veteran system?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Distributed vs. Centralized:&lt;/strong&gt; CVS follows a centralized model, meaning the version history is stored in a central server. If that server crashes without any backups, you lose everything. Git’s distributed approach ensures that every developer has a local copy of the entire history, making it decentralized and significantly safer.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Performance:&lt;/strong&gt; Git’s local operations, thanks to its distributed nature, means that many tasks are faster in Git than CVS. There's no need to communicate with a central server for every tiny operation.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Branch Management:&lt;/strong&gt; Branching in Git is a walk in the park. It's an integral part of the workflow. In CVS, branching is a cumbersome process, often avoided due to its complexity.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Atomic Operations:&lt;/strong&gt; Git operations, like commits, happen atomically. Either they succeed with all changes or fail without any. This isn't always the case with CVS.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data Integrity:&lt;/strong&gt; Git uses a SHA-1 hash to manage data, ensuring the repository’s integrity. If something goes awry, it's immediately noticeable.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Notable Performance Improvements in Git
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Just to pick an example: the concept of “merging” was generally considered to be something really quite painful and hard in most SCM’s. You’d plan your merges, because they were big deals. That’s not acceptable to me, since I commonly do tens of merges a day when in the merge window, and even then, the biggest overhead shouldn’t be the merge itself, it should be testing the result. The “git” part of the merge is just a couple of seconds, it should take me much longer just to write the merge explanation message."&lt;/p&gt;

&lt;p&gt;-- Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Git's inception was driven by the need for performance, but that didn’t stop the community from further refining it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Packed Refs:&lt;/strong&gt; Instead of keeping every single object (commit, tree, blob) as individual files, Git packs them.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Delta Compression:&lt;/strong&gt; When packing, Git identifies the differences between versions and &lt;em&gt;stores just the changes&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Garbage Collection:&lt;/strong&gt; Over time, some objects become obsolete. Git has a garbage collector that removes these unnecessary objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These optimizations, combined with many under-the-hood improvements, ensure that Git remains lightning-fast, even for mammoth repositories. Soon after its creation, developers around the world started contributing. Junio Hamano is one such name that stands out. Within just a few months of Git's inception, he took over its maintenance, ensuring it didn’t remain just a side project but evolved into a robust system.&lt;/p&gt;

&lt;p&gt;Under Hamano's stewardship and with contributions from developers worldwide, Git was not just about committing or branching. It introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Staging Area&lt;/strong&gt;: This intermediary area allows developers to format and review commits before finalizing them.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Remote Repositories&lt;/strong&gt;: With platforms like GitHub and GitLab, remote repositories became a staple, facilitating collaboration among developers globally.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Hooks&lt;/strong&gt;: Custom scripts triggered by important actions, enhancing Git's capabilities and automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's hard to imagine Git without some of these features today, but think about how innovative and unique these were when they were created.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Git": An Odd Name with a Humorous Origin
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“I'm an egotistical bastard, so I name all my projects after myself. First Linux, now Git."&lt;/p&gt;

&lt;p&gt;-- Linus Torvalds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In an email, when asked about the name, he once quipped, “The name 'git' was given by Linus Torvalds when he wrote the very first version. He described the tool as ‘the stupid content tracker’ and the name as (depending on your way): random three-letter combination that is pronounceable, and not actually used by any common UNIX command. The fact that it is a mispronunciation of 'get' may or may not be relevant. Stupid. Contemptible and despicable. Simple. Take your pick from the dictionary of slang.”&lt;/p&gt;

&lt;p&gt;"Git" is British slang for a silly or contemptible person. Silly? Irreverent? Absolutely! But it’s another testament to Torvald’s personality and the informal, community-driven spirit of open-source software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git's Journey Continues
&lt;/h3&gt;

&lt;p&gt;From its rapid inception to becoming the backbone of software versioning, Git’s journey is a testament to open-source power. Its success lies not just in its utility but in a global community that continues to nurture and refine it. Git has transitioned from a quick solution to a kernel project hiccup into an essential tool for individual developers and tech giants alike. Its distributed nature, performance optimizations, and robust branching capabilities make it superior to many version control predecessors.&lt;/p&gt;

&lt;p&gt;So, as you &lt;code&gt;git push&lt;/code&gt; your next big feature or troubleshoot with a &lt;code&gt;git blame&lt;/code&gt;, take a moment to appreciate this tool's history, and perhaps, share its story. Because, in a way, every developer using Git today is part of its ongoing saga.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@yancymin?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Yancy Min&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/842ofHC6MaI?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>linux</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
