ZyVOP Logo
Content That Connects
SeriesAI NewsCategoriesTags
ZyVOP Logo
Content That Connects

Empowering developers and creators with cutting-edge insights, comprehensive tutorials, and innovative solutions for the digital future.

Content

  • Tags
  • Write Article
  • Newsletter

Company

  • About Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Crafted with care for the developer community.

Made with ❤️ by the ZyVOP team
All systems operational
HomeBig-O Notation Explained: The Regex That Took Down Cloudflare for 27 Minutes
👍1

Big-O Notation Explained: The Regex That Took Down Cloudflare for 27 Minutes

Day 1 of the DSA Mastery Series — the concept every post after this one leans on

#Algorithms#datastructures#computerscience#performance#regex
Arpan Singh
Arpan Singh

Senior Developer

July 20, 2026
8 min read
9 views
Big-O Notation Explained: The Regex That Took Down Cloudflare for 27 Minutes

At 13:42 UTC on July 2, 2019, Cloudflare's network stopped answering requests. Not slow. Down.

For 27 minutes, a huge slice of the web returned 502 errors instead of loading.

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.

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.

Cloudflare's own post-mortem 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.

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.

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.

What Big-O Actually Measures

Big-O is not a stopwatch. It doesn't tell you an operation takes "3 milliseconds."

It tells you how the cost of an operation grows as the input grows. That's the whole idea.

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.

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.

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.

Dropping Constants: Reading Big-O Like an Algebra Problem

Say you profile a function and its operation count comes out to:

3n² + 5n + 2

Big-O notation calls this function O(n²). Here's why, term by term.

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

Drop the constant multiplier. Whether it's 3n² or 30n², both describe the same shape 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.

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

The Complexity Ladder

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

Notation

Name

A real example

🟢

O(1)

Constant

Reading arr[5] from an array

🟢

O(log n)

Logarithmic

Binary search on a sorted array

🟢

O(n)

Linear

Scanning a list once for a value

🟡

O(n log n)

Log-linear

Sorting with merge sort or Python's Timsort

🟠

O(n²)

Quadratic

Comparing every pair with nested loops

🔴

O(2ⁿ)

Exponential

Naive recursive Fibonacci — and Cloudflare's regex

🔴

O(n!)

Factorial

Trying every ordering of n items

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

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.

Best Case, Average Case, Worst Case

Big-O by itself usually means the worst case: the input that makes an algorithm work hardest.

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).

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.

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.

Space Complexity: The Other Half of the Bill

Big-O also measures memory, not just time. Same rules, different resource.

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.

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.

Amortized Time: Why "Append" Isn't Always O(1)

Both Python's list.append() and JavaScript's array.push() get described as O(1). That's not the whole story.

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.

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 average cost per append, across many appends, still comes out to O(1). That average has a name: amortized O(1). It's worth knowing before dynamic arrays show up properly in Day 2.

How to Read Your Own Code's Complexity

There's a repeatable process here, simpler than it looks once you've done it a few times.

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

Walk that against a real function:

def has_duplicate_pair(nums):
    for i in range(len(nums)):             # loop 1: n iterations
        for j in range(i + 1, len(nums)):  # loop 2: nested inside loop 1
            if nums[i] == nums[j]:
                return True
    return False

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) = O(n²).

Compare that to a function that looks similar but isn't:

def has_negative_or_over_limit(nums, limit):
    for n in nums:               # loop 1: n iterations
        if n < 0:
            return True
    for n in nums:                # loop 2: separate, not nested
        if n > limit:
            return True
    return False

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

The Benchmarks: What This Looks Like on Real Hardware

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 timeit and process.hrtime.bigint().

O(1): array index access

n

Python (ns/op)

Node.js (ns/op)

1,000

43.3

14.2

10,000

31.3

9.5

100,000

32.1

10.4

1,000,000

30.8

27.2

Flat, as expected. The time doesn't track n 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.

O(log n): binary search

n

Python bisect (ns/op)

Node.js (ns/op)

1,000

114.8

35.8

10,000

121.7

24.9

100,000

156.8

25.4

1,000,000

159.0

28.6

The input grew 1,000x. The time barely moved. That's the entire pitch for logarithmic algorithms in one table.

O(n): linear search, worst case (target never present)

n

Python (µs/op)

Node.js (µs/op)

1,000

6.62

1.11

10,000

59.49

10.14

100,000

616.47

102.58

1,000,000

6,060.19

1,514.06

10x the input, roughly 10x the time, in both languages. Textbook linear.

O(n log n): sorting (Python's Timsort, V8's sort)

n

Python (ms/op)

Node.js (ms/op)

1,000

0.083

0.202

10,000

1.262

2.057

100,000

18.386

26.654

1,000,000

260.294

326.918

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."

O(n²): checking every pair for duplicates, nested loops

n

Python (ms/op)

Node.js (ms/op)

500

3.25

0.09

1,000

13.82

0.34

2,000

53.97

1.32

4,000

219.10

5.29

Double n, 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.

O(2ⁿ) vs O(n): naive vs. memoized Fibonacci

n

Naive Python (ms)

Memoized Python (µs)

Naive Node.js (ms)

Memoized Node.js (µs)

20

0.663

7.63

0.091

3.61

25

8.613

6.01

0.706

4.31

30

78.953

6.84

7.700

5.18

32

206.629

7.15

20.428

5.11

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.

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

The benchmark scripts used to generate the numbers listed above have been uploaded to GitHub for reference.

def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)


def fib_memo(n, cache={}):
    if n <= 1:
        return n
    if n in cache:
        return cache[n]
    cache[n] = fib_memo(n - 1, cache) + fib_memo(n - 2, cache)
    return cache[n]

Where the Regex Went Wrong

Back to Cloudflare, with the vocabulary to actually explain it now.

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 n is the length of the input string, and there's no memoization built in by default.

Small inputs, like most real traffic, never trigger the worst case. That's exactly why the rule passed testing.

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.

What's Next

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.

Every post from here on states the time and space complexity of every operation it covers. This is the post that vocabulary comes from.

Arpan Singh

Arpan Singh

Passionate developer sharing knowledge about modern web technologies and best practices.

Comments (0)

Login to post a comment.

Table of Contents

What Big-O Actually MeasuresDropping Constants: Reading Big-O Like an Algebra ProblemThe Complexity LadderBest Case, Average Case, Worst CaseSpace Complexity: The Other Half of the BillAmortized Time: Why "Append" Isn't Always O(1)How to Read Your Own Code's ComplexityThe Benchmarks: What This Looks Like on Real HardwareO(1): array index accessO(log n): binary searchO(n): linear search, worst case (target never present)O(n log n): sorting (Python's Timsort, V8's sort)O(n²): checking every pair for duplicates, nested loopsO(2ⁿ) vs O(n): naive vs. memoized FibonacciWhere the Regex Went WrongWhat's Next

Stay Updated

Get the latest articles delivered to your inbox.

We respect your privacy. Unsubscribe anytime.

Related Posts

Developer‑Centric AI Hits a Tipping Point: Performance, Open Models, and Scaling Pain | The AI Daily Roundup

Today's headlines show a convergence: AI tools for software development are accelerating performance (Claude Code on Rust‑Bun), opening up (Ollama), but also straining compute (Moonshot) and exposing human‑overreliance (AI advice study). The shift reshapes who wins in the developer market and forces

Read article

The Evolution of TypeScript Compilers: SWC vs TSC

A deep dive into the inner workings of modern JavaScript compilers. Learn why Rust-based tools like SWC and esbuild are replacing TSC, complete with architectural diagrams and benchmarks.

Read article

The Node.js Event Loop Is Not Magic — It's a Contract

Every Node.js performance problem is either an event loop violation or a consequence of one. This is the guide to understanding the contract, diagnosing when it breaks, and building systems that never block.

Read article

Why Your App Is Slow (And It's Not the Database)

Slow APIs with a clean slow query log trace to one of five root causes. Four have nothing to do with query execution. Here's how to identify each one, measure it precisely, and fix it for good.

Read article

Redis Caching in Node.js: The Patterns That Actually Hold Up in Production

A cache hit rate below 50% means your caching strategy is broken, not your hardware. Here's the production Redis playbook — patterns, invalidation, stampede prevention, and the metrics that tell you when things go wrong.

Read article

Popular Tags

#.env.example Node.js#0x profiling#10x faster python scraper tutorial#12-factor#2026#2FA#@nestjs/throttler#AI#AI Anxiety#AI Backend