<?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: Arpan Singh</title>
    <description>The latest articles on DEV Community by Arpan Singh (@arpan_singh_121).</description>
    <link>https://dev.to/arpan_singh_121</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%2F4045545%2Fbe5f845d-2bba-4403-b60e-bf938d72c2da.png</url>
      <title>DEV Community: Arpan Singh</title>
      <link>https://dev.to/arpan_singh_121</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arpan_singh_121"/>
    <language>en</language>
    <item>
      <title>TypeScript 7 Just Shipped: I Checked Five Claims Going Around Against Microsoft's Own Numbers</title>
      <dc:creator>Arpan Singh</dc:creator>
      <pubDate>Tue, 25 Aug 2026 07:12:18 +0000</pubDate>
      <link>https://dev.to/arpan_singh_121/typescript-7-just-shipped-i-checked-five-claims-going-around-against-microsofts-own-numbers-5c50</link>
      <guid>https://dev.to/arpan_singh_121/typescript-7-just-shipped-i-checked-five-claims-going-around-against-microsofts-own-numbers-5c50</guid>
      <description>&lt;p&gt;TypeScript 7 went generally available on July 8, 2026. Within a day, the talking points had already calcified: 10x faster, half the memory, same compiler under the hood, just bump the version and go.&lt;/p&gt;

&lt;p&gt;Some of that is true. Some of it is rounding a real number into a rounder, less accurate one. And one detail — the one that will actually cost teams engineering hours — barely made it into the first wave of coverage at all.&lt;/p&gt;

&lt;p&gt;So instead of another "here's what's new" recap, here are the five claims already circulating, checked one at a time against Microsoft's own release post and the surrounding reporting. Verdicts below, in order of how much they matter to your Monday morning.&lt;/p&gt;




&lt;h2&gt;
  
  
  At a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;The claim&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;"It's roughly 10x faster"&lt;/td&gt;
&lt;td&gt;True — if anything, undersells it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;"It uses about half the memory"&lt;/td&gt;
&lt;td&gt;Overstated — real number is 6–26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;"Same compiler, just written in Go"&lt;/td&gt;
&lt;td&gt;True in behavior, not in effort&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;"Just bump the version and move on"&lt;/td&gt;
&lt;td&gt;Depends entirely on your stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;"Still experimental, not proven at scale"&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Claim 1 · "It's roughly 10x faster"
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Verdict: True — and in the best case, it undersells it.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft's own release post gives a real table, not a marketing bullet. Running TypeScript 6 against TypeScript 7 on five real open-source codebases, at the default setting of 4 parallel type-checker workers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codebase&lt;/th&gt;
&lt;th&gt;TypeScript 6&lt;/th&gt;
&lt;th&gt;TypeScript 7&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VS Code&lt;/td&gt;
&lt;td&gt;125.7s&lt;/td&gt;
&lt;td&gt;10.6s&lt;/td&gt;
&lt;td&gt;11.9x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;139.8s&lt;/td&gt;
&lt;td&gt;15.7s&lt;/td&gt;
&lt;td&gt;8.9x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluesky&lt;/td&gt;
&lt;td&gt;24.3s&lt;/td&gt;
&lt;td&gt;2.8s&lt;/td&gt;
&lt;td&gt;8.7x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;12.8s&lt;/td&gt;
&lt;td&gt;1.47s&lt;/td&gt;
&lt;td&gt;8.7x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tldraw&lt;/td&gt;
&lt;td&gt;11.2s&lt;/td&gt;
&lt;td&gt;1.46s&lt;/td&gt;
&lt;td&gt;7.7x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the &lt;em&gt;conservative&lt;/em&gt; setting. TypeScript 7 ships a new &lt;code&gt;--checkers&lt;/code&gt; flag that controls how many parallel type-checking workers run at once. Push it from the default of 4 to 8 on the same machine, and the VS Code build drops further — to 7.51 seconds, a &lt;strong&gt;16.7x&lt;/strong&gt; speedup over TypeScript 6.&lt;/p&gt;

&lt;p&gt;The editor experience improved by a similar margin, and this is the number that will actually change how a build &lt;em&gt;feels&lt;/em&gt; day to day. Opening a file with an error in the VS Code codebase used to take about 17.5 seconds before the first red squiggle appeared. In TypeScript 7, that's under 1.3 seconds — &lt;strong&gt;over 13x faster&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So "10x" isn't a rounded-up marketing number here. If anything, it's the floor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claim 2 · "It uses about half the memory"
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Verdict: Overstated. The real number is a lot more modest — and still worth having.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the claim worth slowing down on, because it's a good example of how a real figure gets inflated as it travels between blog posts.&lt;/p&gt;

&lt;p&gt;Microsoft's own GA-day benchmark table, on the same five codebases, shows full-build memory usage dropping between &lt;strong&gt;6% and 26%&lt;/strong&gt; — not 50%.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codebase&lt;/th&gt;
&lt;th&gt;TypeScript 6&lt;/th&gt;
&lt;th&gt;TypeScript 7&lt;/th&gt;
&lt;th&gt;Memory delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VS Code&lt;/td&gt;
&lt;td&gt;5.2GB&lt;/td&gt;
&lt;td&gt;4.2GB&lt;/td&gt;
&lt;td&gt;−18%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;td&gt;4.9GB&lt;/td&gt;
&lt;td&gt;4.6GB&lt;/td&gt;
&lt;td&gt;−6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bluesky&lt;/td&gt;
&lt;td&gt;1.8GB&lt;/td&gt;
&lt;td&gt;1.3GB&lt;/td&gt;
&lt;td&gt;−26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright&lt;/td&gt;
&lt;td&gt;1.0GB&lt;/td&gt;
&lt;td&gt;0.9GB&lt;/td&gt;
&lt;td&gt;−11%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tldraw&lt;/td&gt;
&lt;td&gt;0.6GB&lt;/td&gt;
&lt;td&gt;0.5GB&lt;/td&gt;
&lt;td&gt;−15%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of that is a knock on the release. An 18% memory cut on a full VS Code build, stacked on the 11.9x speedup from Claim 1, is a genuinely good trade for a CI runner with fixed RAM.&lt;/p&gt;

&lt;p&gt;But "half the memory" and "18% less memory" are different claims, and only one of them is what Microsoft's own numbers show for a full build. The more dramatic memory story appears to live in steady-state editor sessions rather than full builds — and that distinction got flattened somewhere between the preview-era blog posts and the recaps that followed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claim 3 · "It's basically the same compiler, just written in Go now"
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Verdict: True about behavior. Not even close to true about what it took to get there.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start with what Microsoft actually promises: TypeScript 7 is a &lt;strong&gt;port, not a rewrite&lt;/strong&gt;. The team moved the existing structure, algorithms, and logic from the old JavaScript-based compiler (retroactively named &lt;em&gt;Strada&lt;/em&gt;) into Go, file by file, rather than redesigning the type-checker from scratch. The stated goal was that code compiling cleanly under TypeScript 6 should compile identically under TypeScript 7.&lt;/p&gt;

&lt;p&gt;That faithfulness is exactly why the choice of &lt;strong&gt;Go over Rust&lt;/strong&gt; is worth understanding, rather than shrugging off as a language preference. TypeScript's lead architect, Anders Hejlsberg, has said the team evaluated Rust, C++, and Microsoft's own C# first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust was ruled out for a structural reason, not a taste one.&lt;/strong&gt; The compiler's internal representation — its abstract syntax trees and symbol tables — is full of cyclic references. Rust's ownership model makes that kind of cyclic structure genuinely painful to express without a garbage collector, which would have forced a redesign of the type-checker's core data model before any porting could start.&lt;/p&gt;

&lt;p&gt;Go's garbage-collected, shared-memory model maps directly onto the existing shape: cyclic references just work, and goroutines give cheap parallelism across a single symbol table without redesigning anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C# lost out for a different reason.&lt;/strong&gt; The existing codebase leans heavily functional with minimal use of classes, and Hejlsberg has said C#'s more object-oriented model was a worse structural fit than Go's function-and-struct approach.&lt;/p&gt;

&lt;p&gt;The choice was not universally popular. It spilled across Hacker News, Reddit, and a long GitHub discussion thread, much of it from C# developers asking why Hejlsberg passed over his own language. His answer has been consistent throughout: Go was the lowest-level option that offered native-code performance, fine control over memory layout, cyclic data structures, and mature garbage collection, all at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There's a quieter cost buried in all of this that outlasts the language debate.&lt;/strong&gt; For essentially its entire existence — roughly 14 years — the TypeScript compiler was self-hosted: written in TypeScript, compiling itself. That's over now. The compiler that checks your types is written in a language your types aren't written in, and contributing to it now requires knowing Go, not TypeScript.&lt;/p&gt;

&lt;p&gt;For almost everyone who just uses TypeScript, this changes nothing about your day. For the smaller group who used to read the compiler's own source as a way of learning advanced TypeScript, that specific on-ramp just closed. It's a real if narrow loss — and it's the kind of detail a release note built around benchmark charts has no reason to mention.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claim 4 · "You can just bump the version number and move on"
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Verdict: For some teams, yes. For others, this is the claim that will eat your week.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the one first-look coverage most underserved, because it's not really about speed at all.&lt;/p&gt;

&lt;p&gt;Before TypeScript 7, &lt;code&gt;tsc&lt;/code&gt; was a JavaScript library living in your Node process. Tools called straight into it: &lt;code&gt;ts-jest&lt;/code&gt; called &lt;code&gt;ts.transpileModule()&lt;/code&gt;, &lt;code&gt;typescript-eslint&lt;/code&gt; called &lt;code&gt;ts.createProgram()&lt;/code&gt; to build a full language service, &lt;code&gt;ts-morph&lt;/code&gt; walked the type graph through &lt;code&gt;ts.createCompilerHost()&lt;/code&gt;, and &lt;code&gt;ts-node&lt;/code&gt; hooked Node's module loader and delegated directly to the compiler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript 7's compiler is a separate Go binary&lt;/strong&gt; — the payoff of the port described in Claim 3. It doesn't live in your Node process anymore.&lt;/p&gt;

&lt;p&gt;Anything built on "import typescript and call a function" is now talking to something that no longer exists in that form, and the failures rarely announce themselves as a TypeScript problem. A broken &lt;code&gt;ts-node&lt;/code&gt; setup, for instance, tends to surface as an ESM configuration error — which sends people debugging the wrong layer for a while.&lt;/p&gt;

&lt;p&gt;Three groups of tools are affected differently, and it's worth knowing which one you're in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom AST transformer plugins&lt;/strong&gt; &lt;em&gt;(ts-patch, ttypescript, typia, and similar)&lt;/em&gt; have no JavaScript plugin surface left to call into. There's no clean patch; the real fix is moving transforms to something like SWC or Oxc and letting the Go compiler handle type-checking only.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools that hook the module loader&lt;/strong&gt; — &lt;code&gt;ts-node&lt;/code&gt; being the clearest case — delegate to a JavaScript &lt;code&gt;tsc&lt;/code&gt; that no longer exists in the expected form.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tools built on the programmatic Compiler API&lt;/strong&gt; &lt;em&gt;(typescript-eslint, ts-morph)&lt;/em&gt; are fine for now, but only because Microsoft explicitly deferred this problem. TypeScript 7.0 ships with &lt;strong&gt;no stable API at all&lt;/strong&gt;. That's coming in 7.1, still some months out.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To bridge that last gap, Microsoft published a compatibility package, &lt;code&gt;@typescript/typescript6&lt;/code&gt;, which installs a &lt;code&gt;tsc6&lt;/code&gt; executable and re-exports the old TypeScript 6.0 API. The intended pattern is an npm alias: point &lt;code&gt;typescript&lt;/code&gt; at the 6.0 compatibility package for tools that still need the old API, and add a separate alias so &lt;code&gt;tsc&lt;/code&gt; resolves to TypeScript 7 for your own build and CI steps.&lt;/p&gt;

&lt;p&gt;Tools that never depended on the Compiler API in the first place — Biome and esbuild among them — are simply unaffected, since they ship their own parsers.&lt;/p&gt;

&lt;p&gt;If you're on Vue, MDX, Astro, Svelte, or Angular's Volar-based tooling, you're stuck on TypeScript 6.0 for editor support specifically, since those tools embed TypeScript's own API into their language services and that API doesn't exist yet in 7.0. Microsoft's release notes say this plainly rather than glossing over it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There's also a tsconfig layer to this claim.&lt;/strong&gt; TypeScript 7 hard-adopts TypeScript 6.0's newer defaults and turns a decade of soft deprecations into flat errors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;strict&lt;/td&gt;
&lt;td&gt;Now true by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;module&lt;/td&gt;
&lt;td&gt;Defaults to esnext&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;target: es5&lt;/td&gt;
&lt;td&gt;No longer supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;moduleResolution: node / classic&lt;/td&gt;
&lt;td&gt;No longer supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;module: amd, umd, systemjs, none&lt;/td&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;baseUrl&lt;/td&gt;
&lt;td&gt;Gone — migrate to paths relative to project root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rootDir&lt;/td&gt;
&lt;td&gt;Now defaults to ./ instead of being inferred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;types&lt;/td&gt;
&lt;td&gt;Defaults to an empty array instead of all @types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;downlevelIteration, esModuleInterop: false, alwaysStrict: false&lt;/td&gt;
&lt;td&gt;Rejected outright&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two language-level changes rode along with the tooling shift, and they're worth knowing about even if they're narrow. Template literal type inference now treats a multi-byte character like an emoji as one unit instead of splitting it into a UTF-16 surrogate pair, which breaks any type-level string utility that leaned on the old behavior. And TypeScript's JSDoc-based JavaScript support was reworked to match &lt;code&gt;.ts&lt;/code&gt; semantics more closely, changing how older patterns like &lt;code&gt;@enum&lt;/code&gt; and Closure-style function types get interpreted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So:&lt;/strong&gt; if your stack is &lt;code&gt;tsc&lt;/code&gt; and nothing else, this claim is basically true. If it includes any of the tools above, it's the opposite of true — and the failure mode looks like a config bug rather than a version bump.&lt;/p&gt;




&lt;h2&gt;
  
  
  Claim 5 · "This is still experimental, not proven at real scale"
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Verdict: False. This has already been run hard, by people with a lot to lose.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Beyond Microsoft's own large internal codebases (Loop, Office, PowerBI, Teams, Xbox), the company says it spent the past year working with Bloomberg, Canva, Figma, Google, Lattice, Linear, Miro, Notion, Sentry, Slack, Vanta, Vercel, and VoidZero — the team behind Vite, Rolldown, and Oxc — on pre-release builds of TypeScript 7.&lt;/p&gt;

&lt;p&gt;Some of the reported numbers are specific enough to be more than a testimonial slide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Reported result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;40% less merge queue time; CI type-checking down from ~7.5 min to ~1.25 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vanta&lt;/td&gt;
&lt;td&gt;Up to a 9x speedup on one of their largest projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft (internal team)&lt;/td&gt;
&lt;td&gt;~400 engineering hours/month previously lost to CI waits, recovered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canva&lt;/td&gt;
&lt;td&gt;Time to first error in-editor: ~58s → ~4.8s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Slack's editor had reportedly gotten slow enough that engineers were leaning on CI for full type-checks instead of trusting the local language server.&lt;/p&gt;

&lt;p&gt;Microsoft also reports that the new language server cut failing language-server commands by &lt;strong&gt;more than 80%&lt;/strong&gt; and crashes by &lt;strong&gt;more than 60%&lt;/strong&gt;, compared to TypeScript 6's. That's a meaningful reliability claim stacked on top of the speed one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worth flagging plainly:&lt;/strong&gt; every number in this section is Microsoft measuring Microsoft's software, plus companies Microsoft chose to name. It is not an independently replicated audit, and it would be a mistake to read it as one. But "not proven at scale" doesn't hold up either — this is a different category of evidence than a beta announcement with a demo gif.&lt;/p&gt;




&lt;h2&gt;
  
  
  So — should you upgrade this week?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If your usage is limited to running&lt;/strong&gt; &lt;code&gt;tsc&lt;/code&gt; for type-checking and builds — upgrade now. That's the scenario Microsoft optimized for first, and it carries the least ecosystem risk of anything on this list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If your pipeline includes&lt;/strong&gt; typescript-eslint, ts-morph, ts-node, ts-patch, ttypescript, or a custom transformer — don't do a blind upgrade. Use the npm alias pattern: TypeScript 7 for your own &lt;code&gt;tsc&lt;/code&gt; invocations, the 6.0 compatibility package for anything still calling the old API, until either the tool updates or TypeScript 7.1 ships its stable API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you're on Vue, Astro, Svelte, MDX, or Angular&lt;/strong&gt; — stay on TypeScript 6.0 for editor tooling for now. You can still get the CLI type-check speed from TypeScript 7 without touching your editor's language server plugin.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Either way: don't take my word, Microsoft's word, or anyone else's for the speedup on your specific codebase. &lt;code&gt;npx tsc --version&lt;/code&gt; tells you what you're running, and installing TypeScript 7 alongside your current setup via an alias costs nothing to try before you commit.&lt;/p&gt;




&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Microsoft — &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/" rel="noopener noreferrer"&gt;Announcing TypeScript 7.0&lt;/a&gt; &lt;em&gt;(July 8, 2026)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft — &lt;a href="https://devblogs.microsoft.com/typescript/announcing-typescript-7-0-rc/" rel="noopener noreferrer"&gt;Announcing TypeScript 7.0 RC&lt;/a&gt; &lt;em&gt;(June 18, 2026)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microsoft — &lt;a href="https://devblogs.microsoft.com/typescript/progress-on-typescript-7-december-2025/" rel="noopener noreferrer"&gt;Progress on TypeScript 7 – December 2025&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;microsoft/typescript-go — &lt;a href="https://github.com/microsoft/typescript-go/discussions/411" rel="noopener noreferrer"&gt;Why Go? (GitHub Discussion #411)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The New Stack — &lt;a href="https://thenewstack.io/microsoft-typescript-devs-explain-why-they-chose-go-over-rust-c/" rel="noopener noreferrer"&gt;Microsoft TypeScript Devs Explain Why They Chose Go Over Rust, C#&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DevClass — &lt;a href="https://www.devclass.com/ai-ml/2026/01/28/typescript-inventor-anders-hejlsberg-ai-is-a-big-regurgitator-of-stuff-someone-has-done/4079582" rel="noopener noreferrer"&gt;TypeScript inventor Anders Hejlsberg: AI is "a big regurgitator of stuff someone has done"&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;BeyondIT — &lt;a href="https://beyondit.blog/blogs/three-tools-in-your-typescript-stack-will-break-on-the-7-0-upgrade" rel="noopener noreferrer"&gt;Three Tools in Your TypeScript Stack Will Break on the 7.0 Upgrade&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;webhani — &lt;a href="https://www.webhani.com/blog/typescript-7-breaking-changes" rel="noopener noreferrer"&gt;Preparing for TypeScript 7.0: Breaking Changes and Migration Steps&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visual Studio Magazine — &lt;a href="https://visualstudiomagazine.com/articles/2026/06/22/typescript-7-0-rc-moves-microsofts-go-rewrite-into-the-mainline-compiler.aspx" rel="noopener noreferrer"&gt;TypeScript 7.0 RC Moves Microsoft's Go Rewrite Into the Mainline Compiler&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Neowin — &lt;a href="https://www.neowin.net/news/microsoft-releases-typescript-70-and-its-10x-faster-than-the-previous-version/" rel="noopener noreferrer"&gt;Microsoft releases TypeScript 7.0, and it's 10x faster than the previous version&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://zyvop.com/typescript-7-just-shipped-i-checked-five-claims-going-around-against-microsoft-s-own-numbers-yybq2" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡 For more articles like this, &lt;a href="https://zyvop.com/newsletter" rel="noopener noreferrer"&gt;subscribe to the ZyVOP newsletter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>typescript7</category>
      <category>typescriptgocompiler</category>
      <category>tsgo</category>
      <category>projectcorsa</category>
    </item>
    <item>
      <title>Big-O Notation Explained: The Regex That Took Down Cloudflare for 27 Minutes</title>
      <dc:creator>Arpan Singh</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:09:43 +0000</pubDate>
      <link>https://dev.to/arpan_singh_121/big-o-notation-explained-the-regex-that-took-down-cloudflare-for-27-minutes-1881</link>
      <guid>https://dev.to/arpan_singh_121/big-o-notation-explained-the-regex-that-took-down-cloudflare-for-27-minutes-1881</guid>
      <description>&lt;p&gt;At 13:42 UTC on July 2, 2019, Cloudflare's network stopped answering requests. Not slow. Down.&lt;/p&gt;

&lt;p&gt;For 27 minutes, a huge slice of the web returned 502 errors instead of loading.&lt;/p&gt;

&lt;p&gt;The cause wasn't a DDoS attack or a hardware failure. It was one regular expression, in one firewall rule, pushed to every server Cloudflare owns at the same time.&lt;/p&gt;

&lt;p&gt;That regex had a property called catastrophic backtracking. Feed it the wrong kind of input, and instead of matching in microseconds, the matching time roughly doubles with every extra character.&lt;/p&gt;

&lt;p&gt;Cloudflare's own &lt;a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019" rel="noopener noreferrer"&gt;post-mortem&lt;/a&gt; lays it out plainly: a WAF rule update shipped a regex that ate CPU on every core handling HTTP and HTTPS traffic, worldwide, within minutes of going live.&lt;/p&gt;

&lt;p&gt;That single deployment is the entire reason Big-O notation is worth learning as a working developer, not just memorizing for an interview and forgetting.&lt;/p&gt;

&lt;p&gt;Every data structure and algorithm in this series has a Big-O. Get it wrong, and code that sails through testing can take your production system down exactly like it did for Cloudflare.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Big-O Actually Measures
&lt;/h2&gt;

&lt;p&gt;Big-O is not a stopwatch. It doesn't tell you an operation takes "3 milliseconds."&lt;/p&gt;

&lt;p&gt;It tells you how the &lt;em&gt;cost&lt;/em&gt; of an operation grows as the input grows. That's the whole idea.&lt;/p&gt;

&lt;p&gt;Say a function checks every item in a list once. Double the list, and the function does roughly double the work. That growth pattern, cost scaling linearly with size, is what O(n) describes.&lt;/p&gt;

&lt;p&gt;Big-O deliberately throws away two things: the exact hardware you're running on, and small, constant-size costs. It keeps only the part that matters once your input gets big: the shape of the growth curve.&lt;/p&gt;

&lt;p&gt;That's also why the Cloudflare regex was so dangerous. Its growth curve wasn't linear, or even quadratic. It was exponential, and exponential curves look almost flat for small inputs, then explode the moment the input crosses a threshold. That's exactly the kind of bug that passes every test and detonates in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dropping Constants: Reading Big-O Like an Algebra Problem
&lt;/h2&gt;

&lt;p&gt;Say you profile a function and its operation count comes out to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3n² + 5n + 2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Big-O notation calls this function O(n²). Here's why, term by term.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop the lower-order terms.&lt;/strong&gt; Once &lt;code&gt;n&lt;/code&gt; gets large, &lt;code&gt;5n&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; stop mattering next to &lt;code&gt;3n²&lt;/code&gt;. At n = 1,000,000: &lt;code&gt;3n²&lt;/code&gt; is 3 trillion, &lt;code&gt;5n&lt;/code&gt; is 5 million. A rounding error by comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop the constant multiplier.&lt;/strong&gt; Whether it's &lt;code&gt;3n²&lt;/code&gt; or &lt;code&gt;30n²&lt;/code&gt;, both describe the same &lt;em&gt;shape&lt;/em&gt; of growth: quadratic. The multiplier affects real-world speed, which is a separate conversation from Big-O, but it doesn't change the growth curve.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;3n² + 5n + 2&lt;/code&gt; becomes &lt;code&gt;O(n²)&lt;/code&gt;, every time. This is the most common thing beginners get wrong — they see two nested loops and one extra pass, and try to write &lt;code&gt;O(2n² + n)&lt;/code&gt;. Nobody writes that. It's &lt;code&gt;O(n²)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complexity Ladder
&lt;/h2&gt;

&lt;p&gt;Here's every complexity class you'll run into in this series, best to worst, each with one operation you already use.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Notation&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;A real example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;Constant&lt;/td&gt;
&lt;td&gt;Reading arr[5] from an array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;td&gt;Logarithmic&lt;/td&gt;
&lt;td&gt;Binary search on a sorted array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;Linear&lt;/td&gt;
&lt;td&gt;Scanning a list once for a value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;td&gt;Log-linear&lt;/td&gt;
&lt;td&gt;Sorting with merge sort or Python's Timsort&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟠&lt;/td&gt;
&lt;td&gt;O(n²)&lt;/td&gt;
&lt;td&gt;Quadratic&lt;/td&gt;
&lt;td&gt;Comparing every pair with nested loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;O(2ⁿ)&lt;/td&gt;
&lt;td&gt;Exponential&lt;/td&gt;
&lt;td&gt;Naive recursive Fibonacci — and Cloudflare's regex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;O(n!)&lt;/td&gt;
&lt;td&gt;Factorial&lt;/td&gt;
&lt;td&gt;Trying every ordering of n items&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Green means it scales to millions of items without blinking. Yellow still holds up at real-world scale. Orange starts hurting once &lt;code&gt;n&lt;/code&gt; hits the thousands. Red is the zone Cloudflare's regex lived in — fine for tiny inputs, catastrophic past a threshold.&lt;/p&gt;

&lt;p&gt;Each step down that table is a different universe of slowdown. At n = 20, O(n) and O(2ⁿ) are both fast. At n = 40, O(n) is still instant. O(2ⁿ) has over a trillion steps left to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Case, Average Case, Worst Case
&lt;/h2&gt;

&lt;p&gt;Big-O by itself usually means the &lt;strong&gt;worst case&lt;/strong&gt;: the input that makes an algorithm work hardest.&lt;/p&gt;

&lt;p&gt;Linear search through an unsorted list is O(n) worst case (the value isn't there, or it's last) but O(1) best case (it's the first element checked).&lt;/p&gt;

&lt;p&gt;Quicksort is the classic split-personality algorithm: O(n log n) on average, but O(n²) worst case if it keeps picking a bad pivot on already-sorted data. That's exactly why production sort implementations (Python's Timsort, V8's sort) don't run plain quicksort. They hedge against that worst case directly.&lt;/p&gt;

&lt;p&gt;When someone asks "what's the Big-O of this," they almost always mean the worst case unless they say otherwise. That's the convention this series follows too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Space Complexity: The Other Half of the Bill
&lt;/h2&gt;

&lt;p&gt;Big-O also measures memory, not just time. Same rules, different resource.&lt;/p&gt;

&lt;p&gt;An algorithm that builds a second array the same size as the input is O(n) space. One that tracks a couple of variables, regardless of input size, is O(1) space.&lt;/p&gt;

&lt;p&gt;Recursive functions carry a space cost most beginners miss: every call sits on the call stack until it returns. A recursive function that goes n levels deep uses O(n) space on the stack alone, even without allocating a single array. That'll matter once recursion shows up properly in Day 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Amortized Time: Why "Append" Isn't Always O(1)
&lt;/h2&gt;

&lt;p&gt;Both Python's &lt;code&gt;list.append()&lt;/code&gt; and JavaScript's &lt;code&gt;array.push()&lt;/code&gt; get described as O(1). That's not the whole story.&lt;/p&gt;

&lt;p&gt;Under the hood, both languages back their dynamic arrays with a fixed-size block of memory. Fill it up, and the next append has to allocate a bigger block and copy every existing element over — an O(n) operation.&lt;/p&gt;

&lt;p&gt;Both languages soften this the same way: they over-allocate, growing the backing array by a multiplying factor instead of one slot at a time. Resizes get rarer as the array grows, so the &lt;em&gt;average&lt;/em&gt; cost per append, across many appends, still comes out to O(1). That average has a name: &lt;strong&gt;amortized O(1)&lt;/strong&gt;. It's worth knowing before dynamic arrays show up properly in Day 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Read Your Own Code's Complexity
&lt;/h2&gt;

&lt;p&gt;There's a repeatable process here, simpler than it looks once you've done it a few times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    A[Look at the loops] --&amp;gt; B{Loops nested?}
    B --&amp;gt;|No, sequential| C["Add them: O(a) + O(b), keep the larger term"]
    B --&amp;gt;|Yes| D["Multiply them: O(a) times O(b)"]
    D --&amp;gt; E{Does each step cut&amp;lt;br/&amp;gt;the input in half?}
    E --&amp;gt;|Yes| F["O(log n) for that part"]
    E --&amp;gt;|No| G["O(n) for that part"]
    C --&amp;gt; H{Any recursive calls?}
    G --&amp;gt; H
    F --&amp;gt; H
    H --&amp;gt;|Yes| I["Count total calls made,&amp;lt;br/&amp;gt;not just the visible loop"]
    H --&amp;gt;|No| J["Done, that's your Big-O"]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk that against a real function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_duplicate_pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;             &lt;span class="c1"&gt;# loop 1: n iterations
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;  &lt;span class="c1"&gt;# loop 2: nested inside loop 1
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two loops, nested. That means multiply. Loop 1 runs n times, loop 2 runs roughly n times too (shrinking a bit each pass, a detail Big-O ignores). O(n) times O(n) = &lt;strong&gt;O(n²)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compare that to a function that looks similar but isn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_negative_or_over_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;               &lt;span class="c1"&gt;# loop 1: n iterations
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                &lt;span class="c1"&gt;# loop 2: separate, not nested
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two loops again, but sequential, not nested. Add instead of multiply: O(n) + O(n), the constant drops, and it's &lt;strong&gt;O(n)&lt;/strong&gt;. Seeing "two loops" and assuming O(n²) automatically is one of the most common mistakes in code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmarks: What This Looks Like on Real Hardware
&lt;/h2&gt;

&lt;p&gt;Theory is one thing. Here's what these growth curves look like measured for real — Python 3.12 and Node.js 22, same machine, same inputs, timed with &lt;code&gt;timeit&lt;/code&gt; and &lt;code&gt;process.hrtime.bigint()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  O(1): array index access
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python (ns/op)&lt;/th&gt;
&lt;th&gt;Node.js (ns/op)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;43.3&lt;/td&gt;
&lt;td&gt;14.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;31.3&lt;/td&gt;
&lt;td&gt;9.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;32.1&lt;/td&gt;
&lt;td&gt;10.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;30.8&lt;/td&gt;
&lt;td&gt;27.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Flat, as expected. The time doesn't track &lt;code&gt;n&lt;/code&gt; at all. The small jump at 1,000,000 in the Node.js column isn't a Big-O violation; that's the array outgrowing CPU cache, a real hardware effect Big-O doesn't model. Big-O describes algorithmic growth, not what the cache hierarchy underneath it is doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  O(log n): binary search
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python bisect (ns/op)&lt;/th&gt;
&lt;th&gt;Node.js (ns/op)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;114.8&lt;/td&gt;
&lt;td&gt;35.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;121.7&lt;/td&gt;
&lt;td&gt;24.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;156.8&lt;/td&gt;
&lt;td&gt;25.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;159.0&lt;/td&gt;
&lt;td&gt;28.6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The input grew 1,000x. The time barely moved. That's the entire pitch for logarithmic algorithms in one table.&lt;/p&gt;

&lt;h3&gt;
  
  
  O(n): linear search, worst case (target never present)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python (µs/op)&lt;/th&gt;
&lt;th&gt;Node.js (µs/op)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;6.62&lt;/td&gt;
&lt;td&gt;1.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;59.49&lt;/td&gt;
&lt;td&gt;10.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;616.47&lt;/td&gt;
&lt;td&gt;102.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;6,060.19&lt;/td&gt;
&lt;td&gt;1,514.06&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;10x the input, roughly 10x the time, in both languages. Textbook linear.&lt;/p&gt;

&lt;h3&gt;
  
  
  O(n log n): sorting (Python's Timsort, V8's sort)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python (ms/op)&lt;/th&gt;
&lt;th&gt;Node.js (ms/op)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;0.083&lt;/td&gt;
&lt;td&gt;0.202&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;1.262&lt;/td&gt;
&lt;td&gt;2.057&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;18.386&lt;/td&gt;
&lt;td&gt;26.654&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;260.294&lt;/td&gt;
&lt;td&gt;326.918&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Growing faster than linear, much slower than quadratic — the middle ground n log n describes. Don't read too much into Python edging out Node.js here; it's one machine, one run, and the gap is small enough that engine warm-up explains most of it, not "Python's sort is better."&lt;/p&gt;

&lt;h3&gt;
  
  
  O(n²): checking every pair for duplicates, nested loops
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python (ms/op)&lt;/th&gt;
&lt;th&gt;Node.js (ms/op)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;3.25&lt;/td&gt;
&lt;td&gt;0.09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;13.82&lt;/td&gt;
&lt;td&gt;0.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;td&gt;53.97&lt;/td&gt;
&lt;td&gt;1.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;td&gt;219.10&lt;/td&gt;
&lt;td&gt;5.29&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Double &lt;code&gt;n&lt;/code&gt;, and the time roughly quadruples, in both languages, at every step. That's the signature of O(n²): a pattern you can watch happen in real numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  O(2ⁿ) vs O(n): naive vs. memoized Fibonacci
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Naive Python (ms)&lt;/th&gt;
&lt;th&gt;Memoized Python (µs)&lt;/th&gt;
&lt;th&gt;Naive Node.js (ms)&lt;/th&gt;
&lt;th&gt;Memoized Node.js (µs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0.663&lt;/td&gt;
&lt;td&gt;7.63&lt;/td&gt;
&lt;td&gt;0.091&lt;/td&gt;
&lt;td&gt;3.61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;8.613&lt;/td&gt;
&lt;td&gt;6.01&lt;/td&gt;
&lt;td&gt;0.706&lt;/td&gt;
&lt;td&gt;4.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;78.953&lt;/td&gt;
&lt;td&gt;6.84&lt;/td&gt;
&lt;td&gt;7.700&lt;/td&gt;
&lt;td&gt;5.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;206.629&lt;/td&gt;
&lt;td&gt;7.15&lt;/td&gt;
&lt;td&gt;20.428&lt;/td&gt;
&lt;td&gt;5.11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the table that matters most in the whole post. Going from n=30 to n=32 — adding two — makes the naive version take roughly 2.6x longer, in both languages independently. That 2.6x isn't a coincidence: it's the golden ratio squared, baked into how Fibonacci's recursion tree branches.&lt;/p&gt;

&lt;p&gt;The memoized version barely moves across the entire range. One dictionary, or one &lt;code&gt;Map&lt;/code&gt;, turns an O(2ⁿ) function into an O(n) function. The benchmark shows exactly what that's worth: milliseconds versus microseconds.&lt;/p&gt;

&lt;p&gt;The benchmark scripts used to generate the numbers listed above have been uploaded to &lt;a href="https://github.com/arpansingh9888/dsa-mastery-series" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fib_naive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fib_naive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fib_naive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fib_memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fib_memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fib_memo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where the Regex Went Wrong
&lt;/h2&gt;

&lt;p&gt;Back to Cloudflare, with the vocabulary to actually explain it now.&lt;/p&gt;

&lt;p&gt;A backtracking regex engine, faced with an ambiguous pattern, can end up trying every possible way to match it — the regex equivalent of the naive Fibonacci function above, except &lt;code&gt;n&lt;/code&gt; is the length of the input string, and there's no memoization built in by default.&lt;/p&gt;

&lt;p&gt;Small inputs, like most real traffic, never trigger the worst case. That's exactly why the rule passed testing.&lt;/p&gt;

&lt;p&gt;One request with the right shape of pathological input turned a normally instant regex into an O(2ⁿ) computation, run on every CPU core, on every server, at the same time. That's the outage in one sentence.&lt;/p&gt;

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

&lt;p&gt;Day 2 starts with arrays. There's enough ground there to split it: fundamentals and core operations first, then patterns like two-pointer and sliding window as their own follow-up. Linked lists get the same treatment once we reach them: singly and doubly linked first, then the interview-heavy patterns (reversal, cycle detection, merging) as a separate post.&lt;/p&gt;

&lt;p&gt;Every post from here on states the time and space complexity of every operation it covers. This is the post that vocabulary comes from.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://zyvop.com/big-o-notation-explained-the-regex-that-took-down-cloudflare-for-27-minutes-f1e8y" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡 For more articles like this, &lt;a href="https://zyvop.com/newsletter" rel="noopener noreferrer"&gt;subscribe to the ZyVOP newsletter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>performance</category>
      <category>algorithms</category>
      <category>datastructures</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Arrays Explained from Memory to Big O (With Real Benchmarks)</title>
      <dc:creator>Arpan Singh</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:08:55 +0000</pubDate>
      <link>https://dev.to/arpan_singh_121/arrays-explained-from-memory-to-big-o-with-real-benchmarks-4g8</link>
      <guid>https://dev.to/arpan_singh_121/arrays-explained-from-memory-to-big-o-with-real-benchmarks-4g8</guid>
      <description>&lt;h1&gt;
  
  
  This Loop Should Delete Three Items. It Deletes Two.
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that and guess the output. Three values are under 50: 45, 30, 15. So the obvious guess is &lt;code&gt;[88, 92, 67]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run it, and Python prints &lt;code&gt;[30, 88, 92, 67]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;30 survives. Nothing crashed, no exception, no warning. The code just quietly kept a value it was explicitly told to remove.&lt;/p&gt;

&lt;p&gt;Swap the language and the bug follows you:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&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;0&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="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scores&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="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&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="mi"&gt;1&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="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;scores&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same input, same wrong survivor: &lt;code&gt;[ 30, 88, 92, 67 ]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two different languages, two different removal methods, the exact same failure. That's not a coincidence. It's what an array &lt;em&gt;is&lt;/em&gt; underneath both of them, and Day 2 starts there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What An Array Actually Is
&lt;/h2&gt;

&lt;p&gt;Forget the syntax for a second. At the hardware level, an array is a promise: a single, unbroken block of memory, where every element sits the same distance apart.&lt;/p&gt;

&lt;p&gt;That promise is what makes &lt;code&gt;scores[3]&lt;/code&gt; fast. The computer doesn't search for index 3. It calculates an address directly: start of the block, plus 3 times the size of one element, done. One arithmetic operation, one memory read. That's the entire reason array access is O(1): it's not clever, it's just math.&lt;/p&gt;

&lt;p&gt;Deletion breaks that promise, at least temporarily. Remove the item at index 0, and there's now a gap at the front of an otherwise unbroken block. The only way to restore the promise (a single contiguous run with no holes) is to physically slide every element after the gap back by one position.&lt;/p&gt;

&lt;p&gt;That slide is the whole story. It's an O(n) memory move hiding behind a one-line method call, and it's exactly what desynced the loop above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Loop Skips
&lt;/h2&gt;

&lt;p&gt;Walk &lt;code&gt;scores.remove(score)&lt;/code&gt; through what actually happens in memory, one step at a time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The loop's cursor sits at index 0. It reads &lt;code&gt;45&lt;/code&gt;, sees it's under 50, and removes it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Removing index 0 slides everything left by one slot. The value that used to be at index 1, &lt;code&gt;30&lt;/code&gt;, is now sitting at index 0.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The loop cursor doesn't know that happened. It advances to index 1, as it always does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Index 1 now holds &lt;code&gt;88&lt;/code&gt;, the value that used to be two slots over. &lt;code&gt;30&lt;/code&gt; was never re-examined. It's still in the array when the loop ends, because the loop moved past the exact slot it landed in.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix follows directly from the same mental model. Either walk the array backward, so every shift happens &lt;em&gt;behind&lt;/em&gt; the cursor instead of into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;)&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="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="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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or don't mutate the array you're iterating at all, build a new one instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scores&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version is also the one worth defaulting to. It sidesteps the shifting problem entirely instead of just working around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Arrays vs. What You're Actually Using
&lt;/h2&gt;

&lt;p&gt;C's &lt;code&gt;int arr[10]&lt;/code&gt; is a static array: one fixed-size block, allocated once, that can never grow. Ask for an 11th slot and there's no polite failure. You're writing into memory that belongs to something else.&lt;/p&gt;

&lt;p&gt;Python's &lt;code&gt;list&lt;/code&gt; and JavaScript's &lt;code&gt;Array&lt;/code&gt; aren't that. They're &lt;strong&gt;dynamic arrays&lt;/strong&gt;: a static array under the hood, wrapped in logic that swaps in a bigger block once the old one fills up. Every &lt;code&gt;.append()&lt;/code&gt; or &lt;code&gt;.push()&lt;/code&gt; you've ever called was secretly asking "is there room?" before it ever touched memory.&lt;/p&gt;

&lt;p&gt;That wrapping is why beginners get told arrays are O(1) to append to, and it's &lt;em&gt;mostly&lt;/em&gt; true, but "mostly" is doing real work in that sentence, covered next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every Core Operation, Rated
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;Access by index&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;Direct address math, no searching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟢&lt;/td&gt;
&lt;td&gt;Append / pop at the end&lt;/td&gt;
&lt;td&gt;O(1)*&lt;/td&gt;
&lt;td&gt;No shift needed; *occasionally O(n) — grows past capacity, shrinks past half capacity, more below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟠&lt;/td&gt;
&lt;td&gt;Search (unsorted)&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;No shortcuts — check every slot until found&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;Insert / delete at the start&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;Every remaining element shifts by one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔴&lt;/td&gt;
&lt;td&gt;Insert / delete in the middle&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;Same shift, just for a shorter stretch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern in that table is the entire lesson: &lt;strong&gt;where&lt;/strong&gt; in the array you operate matters as much as &lt;strong&gt;what&lt;/strong&gt; operation you run. The end is cheap. The front is expensive. That's not a Python or JavaScript quirk. It's physics, the same physics that broke the loop above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Asterisk on "O(1) Append"
&lt;/h2&gt;

&lt;p&gt;Dynamic arrays grow by over-allocating: when the backing block fills up, they don't grab one more slot, they grab a noticeably bigger block and copy everything over. That copy is O(n), but it happens rarely enough that the &lt;em&gt;average&lt;/em&gt; cost per append, over many appends, still comes out to O(1). That average has a name: amortized O(1).&lt;/p&gt;

&lt;p&gt;"Rarely enough" isn't hand-waving. CPython's growth pattern is public, and it's checkable from Python itself, since &lt;code&gt;sys.getsizeof()&lt;/code&gt; reports the backing array's real allocated size, not just the list's length. Appending one item at a time and watching that number jump gives you the exact resize points, live, on CPython 3.12:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;List length at resize&lt;/th&gt;
&lt;th&gt;Backing array size (bytes)&lt;/th&gt;
&lt;th&gt;Slots allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;184&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;248&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;312&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;376&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Between each row, appends are just writing into already-reserved space: genuinely O(1), no copying at all. The resize itself, on that one unlucky append, is O(n). Spread that occasional O(n) cost across all the free appends in between, and it averages out to O(1) per call.&lt;/p&gt;

&lt;p&gt;Shrinking runs the same machinery in reverse, on a different trigger than most people guess. &lt;code&gt;pop()&lt;/code&gt; doesn't hand memory back the moment the list gets smaller: CPython only reallocates down once the length falls under half of what's currently allocated, not the instant a single slot goes unused. Pop a 33-item list down to empty and the real shrink points, traced the same way with &lt;code&gt;sys.getsizeof()&lt;/code&gt;, land at lengths 16, 11, 7, 5, 1, and 0. Compare that to the growth table above and the thresholds don't mirror each other; growing and shrinking are governed by two separate rules sharing one function, not a single reversible curve.&lt;/p&gt;

&lt;p&gt;V8 runs the same strategy for JavaScript arrays, over-allocating on growth instead of resizing one slot at a time. That's why &lt;code&gt;.push()&lt;/code&gt; gets the same amortized O(1) label &lt;code&gt;.append()&lt;/code&gt; does.&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Thing V8 Tracks That CPython Doesn't
&lt;/h2&gt;

&lt;p&gt;V8 keeps something called an array's &lt;strong&gt;elements kind&lt;/strong&gt; — a running classification of what's actually in it: all small integers, mixed with floats, holding arbitrary objects, or containing gaps. &lt;a href="https://v8.dev/blog/elements-kinds" rel="noopener noreferrer"&gt;V8's own engineering blog&lt;/a&gt; lays out the mechanism: arrays built with actual gaps in them (skipped indices, or presized with &lt;code&gt;new Array(n)&lt;/code&gt; and filled out of order) get marked &lt;strong&gt;holey&lt;/strong&gt;, and every read from a holey array carries an extra check a gap-free ("packed") array skips.&lt;/p&gt;

&lt;p&gt;That transition only runs one direction: specific to general, packed to holey. It's permanent. Fill every gap in a holey array later, and V8 still won't reclassify it back to packed. The practical takeaway: build arrays in order, front to back, if you want V8 to keep every optimization available to it.&lt;/p&gt;

&lt;p&gt;How much that actually costs in practice is genuinely inconsistent. A plain summation loop over a packed vs. a holey million-element array, benchmarked repeatedly on this machine, came back centered close to even, drifting up to roughly 13% either direction depending on the run, with no consistent winner. Real, but not the dramatic one-directional gap sometimes claimed for it online. The scripts to run that comparison yourself are in the attached zip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmarks
&lt;/h2&gt;

&lt;p&gt;Same rule as Day 1: no number below is estimated. Python 3.12 and Node.js 22, timed with &lt;code&gt;time.perf_counter()&lt;/code&gt; and &lt;code&gt;process.hrtime.bigint()&lt;/code&gt;, each operation paired with its opposite (append then pop, insert then remove) so the array's size stays constant across the whole run.&lt;/p&gt;

&lt;h3&gt;
  
  
  End of the array: append / pop
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python append (ns)&lt;/th&gt;
&lt;th&gt;Python pop (ns)&lt;/th&gt;
&lt;th&gt;Node push+pop (ns, combined)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;44.9&lt;/td&gt;
&lt;td&gt;31.0&lt;/td&gt;
&lt;td&gt;10.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;31.4&lt;/td&gt;
&lt;td&gt;31.0&lt;/td&gt;
&lt;td&gt;1.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;32.1&lt;/td&gt;
&lt;td&gt;30.9&lt;/td&gt;
&lt;td&gt;2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;31.7&lt;/td&gt;
&lt;td&gt;34.6&lt;/td&gt;
&lt;td&gt;1.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Flat across four orders of magnitude, in both languages. That's the empirical signature of O(1). The Node numbers look almost too fast to be real; that's V8's JIT compiling this exact push-then-pop pattern more aggressively the longer the process runs, not a language-superiority result. Treat the flatness as the finding, not the specific nanosecond count.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start of the array: insert / delete
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;Python insert(0) (µs)&lt;/th&gt;
&lt;th&gt;Python pop(0) (µs)&lt;/th&gt;
&lt;th&gt;Node unshift (µs)&lt;/th&gt;
&lt;th&gt;Node shift (µs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;0.26&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;2.60&lt;/td&gt;
&lt;td&gt;1.84&lt;/td&gt;
&lt;td&gt;1.85&lt;/td&gt;
&lt;td&gt;0.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;24.77&lt;/td&gt;
&lt;td&gt;17.97&lt;/td&gt;
&lt;td&gt;18.65&lt;/td&gt;
&lt;td&gt;18.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;364.64&lt;/td&gt;
&lt;td&gt;342.58&lt;/td&gt;
&lt;td&gt;362.43&lt;/td&gt;
&lt;td&gt;348.51&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;10x the input keeps landing close to 10x the time, in every column. That's the same linear signature the O(n) search benchmark showed on Day 1, now showing up for a different operation entirely. (Node's &lt;code&gt;shift&lt;/code&gt; at n=10,000 reads oddly low next to its neighbors — at that size the fixed cost of the function call itself is still competing with the shift cost, so the line hasn't straightened out yet. It does by 100,000.)&lt;/p&gt;

&lt;p&gt;Both scripts behind every number in this post (the Python and Node versions, plus the resize-point tracer and the packed/holey comparison) are packaged in &lt;a href="https://github.com/arpansingh9888/dsa-mastery-series/tree/master/arrays-benchmarks" rel="noopener noreferrer"&gt;arrays-ops-benchmark.zip&lt;/a&gt;, attached to this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the Bug
&lt;/h2&gt;

&lt;p&gt;The loop at the top of this post wasn't a trick question. It's the single most common way arrays bite working developers, in any language that has them, and it only looks mysterious until you've internalized what deletion actually costs.&lt;/p&gt;

&lt;p&gt;Once you know an array is a contiguous block, and that removing from the middle means physically sliding everything after it, the loop's behavior stops being surprising. It's not broken. It's doing exactly what shifting a block of memory always does. The bug was ever expecting the cursor to notice.&lt;/p&gt;

&lt;p&gt;Try it yourself before the next post: take the backward-iteration fix above, and instead of removing values under 50, remove every &lt;em&gt;duplicate&lt;/em&gt; value from a list, keeping just the first occurrence of each. Same shifting mechanics, one extra piece of state to track. It's a good gut-check for whether the mental model actually stuck.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://zyvop.com/arrays-explained-from-memory-to-big-o-with-real-benchmarks-y8b8q" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;💡 For more articles like this, &lt;a href="https://zyvop.com/newsletter" rel="noopener noreferrer"&gt;subscribe to the ZyVOP newsletter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>python</category>
      <category>node</category>
      <category>programmingfundamentals</category>
      <category>arrays</category>
    </item>
  </channel>
</rss>
