{
    "archive_path": "archive/1755996525.864487",
    "base_url": "samwho.dev/big-o",
    "basename": "",
    "bookmarked_date": "2025-08-24 00:48",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/samwho.dev/big-o",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=samwho.dev",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "samwho.dev",
    "downloaded_at": "2025-08-24T00:48:48.580224+00:00",
    "downloaded_datestr": "2025-08-24 00:48",
    "extension": "",
    "hash": "16KEYET0A53MVBB0E4RQ",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://samwho.dev/big-o/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-24T00:50:38.963914+00:00",
                "index_texts": null,
                "output": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://samwho.dev/big-o/']' timed out after 60 seconds",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:38.889002+00:00",
                "status": "failed"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2025-08-24T00:49:02.984431+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:48:57.044267+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=samwho.dev"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-24T00:48:51.971501+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:48:49.353412+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-24T00:48:52.217614+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:48:52.021252+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2025-08-24T00:49:30.859337+00:00",
                "index_texts": [
                    " (https://octodon.social/@samwho)  (https://hachyderm.io/@samwho)  Big O (https://samwho.dev/css/reset.css?h=6f7861e8f1e8cd8577de) (https://samwho.dev/css/monokai.css?h=6bba23eb8c9e392cb1c9) (https://samwho.dev/css/main.css?h=5868da89344f245266b8) (RSS) (https://samwho.dev/rss.xml) (https://samwho.dev/css/big-o.css?h=a4923cea86b256011d74)             B   i   g       O    Big O notation is a way of describing the performance of a function without\nusing time. Rather than timing a function from start to finish, big O describes\nhow the time grows as the input size increases. It is used to help understand\nhow programs will perform across a range of inputs. In this post I'm going to cover 4 frequently-used categories of big O notation: constant , logarithmic , linear , and quadratic . Don't worry if\nthese words mean nothing to you right now. I'm going to talk about them in\ndetail, as well as visualise them, throughout this post. (A picture of a cartoon husky called \"Doe\") Before you scroll! This post has been sponsored by the wonderful folks at (https://ittybit.com/?ref=sam) ittybit , and their API for working\n  with videos, images, and audio. If you need to store, encode, or get\n  intelligence from the media files in your app, check them out!    # Iterating Consider this JavaScript function that calculates the sum of 1 to n. function sum  ( n )  {    let total   = 0  ;    for ( let i   = 1  ; i <= n ; i ++ ) {     total += i ;     }     return total ;    }      I'm going to pass in 1 billion, written using the shorthand 1e9 , so it takes a\nnoticeable amount of time to run. Press the     button below to\nmeasure how long it takes to calculate the sum of 1 to 1 billion. (sum(1e9)) sum(1e9)     00:00.000   On my laptop this takes just under 1 second. The time you get may be different,\nand it may vary by a few tens of milliseconds each time you run it. This is\nexpected. Timing code this way, by taking the start time and the end time and finding the\ndifference, is called wall-clock time . How long do you think 2 billion, 2e9 , will take? (sum(2e9)) sum(2e9)     00:00.000   It takes about twice as long. The code loops n times and does a single\naddition each time. If we passed in 3 billion, we would expect the execution\ntime to increase by a factor of three. (A picture of an old husky dog, with a proud facial expression) The relationship between a function's input and how long it takes to execute\n  is called its time complexity , and big O notation is how we\n  communicate what the time complexity of a function is.    Play around with the buttons below. Each bar adds an extra 1 billion to the n that we pass to sum , so the    1e9  button calls sum(1e9) ,\nthe    2e9  button calls sum(2e9) , and so on. You should see 2e9 take about twice as long as 1e9 , and 3e9 take about three times as\nlong as 1e9 .      1e9       2e9       3e9       4e9       5e9    Note : On some browsers, you might experience bars taking a lot longer\n    than they should. I wasn't able to figure out why before publishing. If a\n    bar seems way off what you would expect, try re-running it.   Because sum 's wall-clock time grows at the same rate as n , e.g. sum(20) would take twice as long as sum(10) , we say that sum is a \"linear\"\nfunction. It has a big O of n, or O(n) . (A picture of a cartoon husky puppy called \"Haskie\" looking confused.) Why do we use that syntax: O(n) ? What is O? Why those brackets?    The \"O\" stands for \"order,\" short for \"order of growth.\" Said out loud it would\nbe: \"the order of growth of the sum function is n.\" O(n) is a compact way\nto write that. The notation was created by the German mathematician Paul\nBachmann in 1894.  Also, the \"O\" (letter) might look like a 0 (number) in some\ntypefaces.  It is always the letter O. A different way to sum the numbers from 1 to n is to use the formula (n*(n+1))/2 . Carl Friedrich Gauss discovered this formula in the early 1800s, and\nit's a clever way for us to avoid having to loop over all of the numbers. Here's the result of this formula with the numbers from 1 to 5. In each case\nthe result should be the same as doing, e.g. 1+2+3+4+5 for n=5 . (1*2)/2 = 2/2  = 1  (2*3)/2 = 6/2  = 3  (3*4)/2 = 12/2 = 6  (4*5)/2 = 20/2 = 10  (5*6)/2 = 30/2 = 15   Here's how sum would look if it used that formula instead of the loop we had\nbefore: function sum  ( n )  {    return ( n * ( n + 1 ) ) / 2 ;    }      How do you think the wall-clock time of this function changes as n increases?\nThe next two examples differ by a factor of 100 . (sum(1e9)) sum(1e9)     00:00.000   (sum(100e9)) sum(100e9)     00:00.000   This example isn't broken. Both of these functions take almost no time to at\nall.  The variance in timing is caused by the browser, and the unpredictability\nof computers, not the sum function.  Running each example a few times, you\nshould see that the wall-clock time hovers around the same value for both. We call functions like this, whose wall-clock time is about the same no matter\nwhat input you give it, constant or O(1) . (A picture of a husky dog called \"Haskie\", looking triumphant) Wow, so we improved our sum function from O(n) to O(1) ! It\n  always runs instantly now!    We did! Though it is crucial to remember that O(1) doesn't always mean\n\"instant.\" It means that the time taken doesn't increase with the size\nof the input. In the case of our new sum function, it's more or less\ninstant. But it's possible for an O(1) algorithm to take several minutes or\neven hours, depending on what it does. It's also possible for an O(n) algorithm to be faster than an O(1) algorithm for some of its inputs. Eventually, though, the O(1) algorithm\nwill outpace the O(n) algorithm as the input size grows. Play with the\nslider below to see an example of that.    (O(n)) (O(1))       (0) (A picture of a cartoon husky puppy called \"Haskie\" looking confused.) The O(1) line is always at 20 in that graph, so why don't we say O(20) instead?    The purpose of big O notation is to describe the relationship between the input\nand the wall-clock time of a function. It isn't concerned with what that\nwall-clock time ends up being, only with how it grows. Big O always describes growth in the smallest terms possible.  It would quickly\nget messy if the world had to figure out what number to put inside the brackets\nfor every function, and have those numbers be correct relative to each other.\nLikewise, linear functions are always O(n) and never O(2n) or O(n +\n1) , because O(n) is the smallest linear term. # Sorting Let's move away from sum and talk about a different algorithm: bubble\nsort . The idea behind bubble sort is that you loop over the input array and swap\nelements next to each other if they are not in the desired order. You do this\nuntil you're able to complete a loop through the array without making any swaps.\nIt's called bubble sort because of the way numbers \"bubble\" up to their correct\nposition. Below is a JavaScript implementation of this algorithm. We're going to sort\nthese numbers in ascending order, so the desired result is 1, 2, 3, 4, 5. You can step through this code and watch the array get sorted using the\ncontrols.      steps forwards 1 line at a time,      steps backwards,     automatically\nadvances forward 1 line every second, and    resets back to\nthe start. a = i i + 1  3 2 5 4 1      (0.2) .2x (0.5) .5x (1) 1x (2) 2x (4) 4x (8) 8x                    function bubbleSort ( a ) {    while ( true ) {    let swapped = false ;    for ( let i = 0 ; i < a . length  - 1   ; i ++ ) {    if ( a [ i ]  > a [ i + 1  ]   ) {    [ a [ i ]  , a [ i + 1  ]  ] = [ a [ i + 1  ]  , a [ i ]  ]  ;    swapped = true ;    }    }    if ( ! swapped  ) break ;    }    return a ;    }       Now you've had a look at the algorithm in action, what do you think its big O\nis? If the array is already sorted, the algorithm loops through once, does no swaps,\nand exits. This would be O(n) . But if the array is in any other order,\nwe need to loop through more than once. In the worst case, where the array is\nin reverse order, we would have to loop through n times in order to move the\nsmallest element from the end to the start. Looping through the n elements of our array n times results in n*n operations, or n^2 . That means bubble sort is an O(n^2) algorithm .\nSometimes called quadratic.  Because it's common for an algorithm's performance to depend not just on the size of the input, but also its arrangement, big O notation always describes\nthe worst-case scenario . (A picture of an old husky dog) You may sometimes see the Greek letter \"theta\", \u0398, instead of the letter O in\nbig O notation. This is used when the best and worst case have the same order\nof growth. We can't use it for bubble sort because the best case is O(n) and the worst case is O(n^2) .    Below is another way to visualise bubble sort. It starts off with the array 5,\n4, 3, 2, 1 from top to bottom. The end state should be 1, 2, 3, 4, 5 top to\nbottom. Each      step forward represents a full loop through\nthe array, potentially involving multiple swaps. Use Best , Worst , and Rand to see different initial configurations.         5   4   3   2   1  Best Worst Rand                If you played around with Rand a few times you'll have noticed that you\ndo sometimes get only a couple of iterations. Despite this, bubble sort is still O(n^2) because in the worst case you'll have to iterate over the array n times. # Searching The last algorithm I want to talk about is binary search . Let's start with a game. Think of a number between 1 and 100. I'm going\nto try and guess what it is. Use the buttons below to tell me if your number is\nhigher or lower than my guess. Guess #1 Is your number... 50 Lower Correct! Higher    What I'm doing is starting in the middle of the range, 50, and eliminating half\nof the possibilities with each guess. This is a binary search . Using this method it will never take more than 7 guesses to find your number.\nThis is because we start with 100 possibilities and half of the possibilities\nare ruled out with each guess. The table below shows the guessing pattern for all numbers between 1 and 100,\nuse the slider to choose a number. Target: 2   Guess # Possibilities I guess You say   1 100 50 Lower  2 49 25 Lower  3 24 12 Lower  4 11 6 Lower  5 5 3 Lower  6 2 1 Higher  7 1 2 Correct      When it's possible to eliminate a fraction of possibilities with every step\nof an algorithm, we call it logarithmic . That means that binary search is\nan O(log n) algorithm. Below is a graph of the number of guesses I would need in order to figure out\nyour number for all of the numbers between 1 and 1000. (Your number) (Guesses)    (Guesses)     (0) Every time your number doubles, I need 1 extra guess to find it. If you were to\npick a number between 1 and 1 billion, I would need 31 guesses at most to find\nit. Logarithmic growth is really slow! Below is a graph comparing it to O(n) and O(n^2) , which both grow much faster.    (O(n^2)) (O(n)) (O(log n))        (0) # Putting this knowledge to work In the previous sections of this post I've described the difference between O(1) , O(log n) , O(n) , and O(n^2) algorithms. Let's have a look\nat some situations you might encounter while writing code and what you can do to\nimprove your time complexity. # Finding an item in a list Here's a function that checks if a list contains a specific item. function contains  ( items , target )  {    for ( const item    of items ) {     if ( item === target ) {      return true ;      }      }     return false ;    }      If you're looking up items in the same list lots of times, you might want to\nconsider using a data structure that allows for faster lookups, such as a Set . Modern browsers implement Set in a way that gives O(1) time complexity for lookups. However, don't do this: function contains  ( items , target )  {    const itemSet   = new Set ( items )   ;    return itemSet . has  ( target ) ;    }      Building the new Set(items) is an O(n) operation! This is because the Set constructor loops over all items in the array to add them to the set. You\nneed to weigh the cost of this upfront work against the potential savings from\nfaster lookups. const items   = new Set ( [ \" apple\"  , \" banana\"  , \" cherry\"  ]  )   ;  items . has  ( \" banana\"  ) //  true, and O(1)!    # Loop an array with indexes The code below contains a common mistake I've seen dozens of times in production\ncode.  Have a read and see if you can answer: What is the big O of this function? How could we improve it?  function buildList  ( items )  {    const output   = [ ]   ;    for ( const item    of items ) {     const index   = items . indexOf  ( item )  ;     output . push  ( ` Item ${  index + 1  }  : ${  item  }  `  ) ;     }     return output . join  ( \" \\n \"  ) ;    }      The problem is calling .indexOf inside the loop. The .indexOf function is an O(n) operation! It works by looping over the array until it finds the target\nelement, returning null if it doesn't. Calling it inside the loop makes the\noverall big O of our buildList function O(n^2) ! To fix this we can loop using an index. Looking up an element in an array by its\nindex is O(1) , so the overall big O of the function is reduced to O(n) . function buildList  ( items )  {    const output   = [ ]   ;    for ( let i   = 0  ; i < items . length ; i ++ ) {     output . push  ( ` Item ${  i + 1  }  : ${  items [ i ]   }  `  ) ;     }     return output . join  ( \" \\n \"  ) ;    }      You can also achieve the same result with JavaScript's .forEach((item, index) => {}) method on arrays, or Object.entries() . # Caching intermediate results Consider this function to calculate the factorial of a number. A factorial in\nmathematics is written as, e.g., 5! to represent 5*4*3*2*1 or 3! to\nrepresent 3*2*1 . function factorial  ( n )  {    if ( n === 0 ) {     return 1 ;     }     return n * factorial  ( n - 1 ) ;    }      This function has a time complexity of O(n) , but most calls to this function\nare going to redo work we've done before. Calling factorial(4) and then factorial(5) will mean factorial(4) is calculated twice. We can cache the result of each calculation to avoid this redundant work. const cache   = new Map ( )   ;  function factorial  ( n )  {    if ( cache . has  ( n ) ) {     return cache . get  ( n ) ;     }     if ( n === 0 ) {     return 1 ;     }     const result   = n * factorial  ( n - 1 )  ;    cache . set  ( n , result ) ;    return result ;    }      This makes use of the O(1) time complexity for lookups in a Map .  It\ndoesn't change the worst case time complexity of the factorial function, but\nit does make the average case faster at the cost of increased memory usage. (A picture of an old husky dog, with a concerned facial expression) When it comes to performance, remember that you can't ever take what you read\n  online at face value. Always test your code before and after changes\n  to make sure you're actually improving it!    # Conclusion Let's recap what we've learned: Big O notation describes the relationship between a function's input and\nits wall-clock time . From slowest growth to fastest growth we saw examples of: O(1) , constant time (best!) O(log n) , logarithmic time O(n) , linear time O(n^2) , quadratic time   We can improve the time complexity of the code we write by making better\nalgorithmic choices and avoiding common pitfalls.  These posts take me a long time to write, and they wouldn't be possible without\nthe support of my family, friends, sponsors, and reviewers. I'm so grateful to\nall of you\u2014you know who you are\u2014for making it possible for me to make these. If you enjoyed this post, I've written a bunch more similar that you can find (/) on my homepage . I also love hearing from folks that have questions\nor feedback, you can reach me via (mailto:big-o@samwho.dev) email ,\non (https://bsky.app/profile/samwho.dev) Bluesky , or anonymously via\nmy (/ping) silly little ping service . I'll leave you with one last graph comparing all of the time complexities we\ncovered in this post.    (O(1), constant) (O(log n), logarithmic) (O(n), linear) (O(n^2), quadratic)         (0)   (Sam Rose) (https://twitter.com/samwhoo)        (https://bsky.app/profile/samwho.dev)   Enjoyed this post? Consider subscribing to get updates about new posts\n        via email!  Alternatively, you can (/rss.xml) subscribe via RSS .  (your@email.address) (Subscribe) (https://buttondown.email/refer/samwho) powered by buttondown      "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:30.732877+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2025-08-24T00:49:38.720557+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:32.994700+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2025-08-24T00:49:30.624297+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:24.788693+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmp4sh3v4e2",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2025-08-24T00:49:11.570708+00:00",
                "index_texts": [
                    "B\n            i\n            g\n        \n\n        \n          \n            \n          \n        \n        O\n      \nBig O notation is a way of describing the performance of a function without\nusing time. Rather than timing a function from start to finish, big O describes\nhow the time grows as the input size increases. It is used to help understand\nhow programs will perform across a range of inputs.\nIn this post I'm going to cover 4 frequently-used categories of big O notation:\nconstant, logarithmic, linear, and quadratic. Don't worry if\nthese words mean nothing to you right now. I'm going to talk about them in\ndetail, as well as visualise them, throughout this post.\n\n#\nIterating\nConsider this JavaScript function that calculates the sum of 1 to n.\nfunction sum(n) {\n  let total = 0;\n  for (let i = 1; i <= n; i++) {\n    total += i;\n  }\n  return total;\n}\n\nI'm going to pass in 1 billion, written using the shorthand 1e9, so it takes a\nnoticeable amount of time to run. Press the \n      \n      \n     button below to\nmeasure how long it takes to calculate the sum of 1 to 1 billion.\nsum(1e9)\nOn my laptop this takes just under 1 second. The time you get may be different,\nand it may vary by a few tens of milliseconds each time you run it. This is\nexpected.\nTiming code this way, by taking the start time and the end time and finding the\ndifference, is called wall-clock time.\nHow long do you think 2 billion, 2e9, will take?\nsum(2e9)\nIt takes about twice as long. The code loops n times and does a single\naddition each time. If we passed in 3 billion, we would expect the execution\ntime to increase by a factor of three.\n\nPlay around with the buttons below. Each bar adds an extra 1 billion to the n\nthat we pass to sum, so the \n      \n      \n    1e9 button calls sum(1e9),\nthe \n      \n      \n    2e9 button calls sum(2e9), and so on. You should see\n2e9 take about twice as long as 1e9, and 3e9 take about three times as\nlong as 1e9.\n\n  \n  \n  \n  \n  \n  \n\n\nBecause sum's wall-clock time grows at the same rate as n, e.g. sum(20)\nwould take twice as long as sum(10), we say that sum is a \"linear\"\nfunction. It has a big O of n, or O(n).\n\nThe \"O\" stands for \"order,\" short for \"order of growth.\" Said out loud it would\nbe: \"the order of growth of the sum function is n.\" O(n) is a compact way\nto write that. The notation was created by the German mathematician Paul\nBachmann in 1894.  Also, the \"O\" (letter) might look like a 0 (number) in some\ntypefaces.  It is always the letter O.\nA different way to sum the numbers from 1 to n is to use the formula (n*(n+1))/2. Carl Friedrich Gauss discovered this formula in the early 1800s, and\nit's a clever way for us to avoid having to loop over all of the numbers.\nHere's the result of this formula with the numbers from 1 to 5. In each case\nthe result should be the same as doing, e.g. 1+2+3+4+5 for n=5.\n\n(1*2)/2 = 2/2  = 1\n(2*3)/2 = 6/2  = 3\n(3*4)/2 = 12/2 = 6\n(4*5)/2 = 20/2 = 10\n(5*6)/2 = 30/2 = 15\n\nHere's how sum would look if it used that formula instead of the loop we had\nbefore:\nfunction sum(n) {\n  return (n * (n + 1)) / 2;\n}\n\nHow do you think the wall-clock time of this function changes as n increases?\nThe next two examples differ by a factor of 100.\nsum(1e9)\nsum(100e9)\nThis example isn't broken. Both of these functions take almost no time to at\nall.  The variance in timing is caused by the browser, and the unpredictability\nof computers, not the sum function.  Running each example a few times, you\nshould see that the wall-clock time hovers around the same value for both.\nWe call functions like this, whose wall-clock time is about the same no matter\nwhat input you give it, constant or O(1).\n\nWe did! Though it is crucial to remember that O(1) doesn't always mean\n\"instant.\" It means that the time taken doesn't increase with the size\nof the input. In the case of our new sum function, it's more or less\ninstant. But it's possible for an O(1) algorithm to take several minutes or\neven hours, depending on what it does.\nIt's also possible for an O(n) algorithm to be faster than an O(1)\nalgorithm for some of its inputs. Eventually, though, the O(1) algorithm\nwill outpace the O(n) algorithm as the input size grows. Play with the\nslider below to see an example of that.\n\n  \n  \n    \n    \n  \n  \n    \n\n\n\n\nThe purpose of big O notation is to describe the relationship between the input\nand the wall-clock time of a function. It isn't concerned with what that\nwall-clock time ends up being, only with how it grows.\nBig O always describes growth in the smallest terms possible.  It would quickly\nget messy if the world had to figure out what number to put inside the brackets\nfor every function, and have those numbers be correct relative to each other.\nLikewise, linear functions are always O(n) and never O(2n) or O(n +\n1), because O(n) is the smallest linear term.\n#\nSorting\nLet's move away from sum and talk about a different algorithm: bubble\nsort.\nThe idea behind bubble sort is that you loop over the input array and swap\nelements next to each other if they are not in the desired order. You do this\nuntil you're able to complete a loop through the array without making any swaps.\nIt's called bubble sort because of the way numbers \"bubble\" up to their correct\nposition.\nBelow is a JavaScript implementation of this algorithm. We're going to sort\nthese numbers in ascending order, so the desired result is 1, 2, 3, 4, 5.\nYou can step through this code and watch the array get sorted using the\ncontrols. \n      \n      \n      \n     steps forwards 1 line at a time, \n      \n      \n      \n     steps backwards, \n      \n      \n     automatically\nadvances forward 1 line every second, and \n      \n     resets back to\nthe start.\n\n\n  \n  function bubbleSort(a) {\n  while(true) {\n    let swapped = false;\n    for (let i = 0; i < a.length - 1; i++) {\n      if (a[i] > a[i+1]) {\n        [a[i], a[i+1]] = [a[i+1], a[i]];\n        swapped = true;\n      }\n    }\n    if (!swapped) break;\n  }\n  return a;\n}\n\nNow you've had a look at the algorithm in action, what do you think its big O\nis?\nIf the array is already sorted, the algorithm loops through once, does no swaps,\nand exits. This would be O(n). But if the array is in any other order,\nwe need to loop through more than once. In the worst case, where the array is\nin reverse order, we would have to loop through n times in order to move the\nsmallest element from the end to the start.\nLooping through the n elements of our array n times results in n*n\noperations, or n^2. That means bubble sort is an O(n^2) algorithm.\nSometimes called quadratic.\nBecause it's common for an algorithm's performance to depend not just on the\nsize of the input, but also its arrangement, big O notation always describes\nthe worst-case scenario.\n\nBelow is another way to visualise bubble sort. It starts off with the array 5,\n4, 3, 2, 1 from top to bottom. The end state should be 1, 2, 3, 4, 5 top to\nbottom. Each \n      \n      \n      \n     step forward represents a full loop through\nthe array, potentially involving multiple swaps. Use Best,\nWorst, and Rand to see different initial configurations.\n54321\nIf you played around with Rand a few times you'll have noticed that you\ndo sometimes get only a couple of iterations. Despite this, bubble sort is still\nO(n^2) because in the worst case you'll have to iterate over the array\nn times.\n#\nSearching\nThe last algorithm I want to talk about is binary search.\nLet's start with a game. Think of a number between 1 and 100. I'm going\nto try and guess what it is. Use the buttons below to tell me if your number is\nhigher or lower than my guess.\nGuess #1Is your number...50\nWhat I'm doing is starting in the middle of the range, 50, and eliminating half\nof the possibilities with each guess. This is a binary search.\nUsing this method it will never take more than 7 guesses to find your number.\nThis is because we start with 100 possibilities and half of the possibilities\nare ruled out with each guess.\nThe table below shows the guessing pattern for all numbers between 1 and 100,\nuse the slider to choose a number.\nTarget: 2\u00a0\u00a0Guess #PossibilitiesI guessYou say110050Lower24925Lower32412Lower4116Lower553Lower621Higher712Correct\nWhen it's possible to eliminate a fraction of possibilities with every step\nof an algorithm, we call it logarithmic. That means that binary search is\nan O(log n) algorithm.\nBelow is a graph of the number of guesses I would need in order to figure out\nyour number for all of the numbers between 1 and 1000.\n\n  \n    \n    \n  \n  \n    \n\n\n\nEvery time your number doubles, I need 1 extra guess to find it. If you were to\npick a number between 1 and 1 billion, I would need 31 guesses at most to find\nit. Logarithmic growth is really slow! Below is a graph comparing it to\nO(n) and O(n^2), which both grow much faster.\n\n  \n  \n    \n    \n  \n  \n    \n\n\n\n#\nPutting this knowledge to work\nIn the previous sections of this post I've described the difference between\nO(1), O(log n), O(n), and O(n^2) algorithms. Let's have a look\nat some situations you might encounter while writing code and what you can do to\nimprove your time complexity.\n#\nFinding an item in a list\nHere's a function that checks if a list contains a specific item.\nfunction contains(items, target) {\n  for (const item of items) {\n    if (item === target) {\n      return true;\n    }\n  }\n  return false;\n}\n\nIf you're looking up items in the same list lots of times, you might want to\nconsider using a data structure that allows for faster lookups, such as a\nSet. Modern browsers implement Set in a way that gives O(1)\ntime complexity for lookups.\nHowever, don't do this:\nfunction contains(items, target) {\n  const itemSet = new Set(items);\n  return itemSet.has(target);\n}\n\nBuilding the new Set(items) is an O(n) operation! This is because the\nSet constructor loops over all items in the array to add them to the set. You\nneed to weigh the cost of this upfront work against the potential savings from\nfaster lookups.\nconst items = new Set([\"apple\", \"banana\", \"cherry\"]);\nitems.has(\"banana\") // true, and O(1)!\n\n#\nLoop an array with indexes\nThe code below contains a common mistake I've seen dozens of times in production\ncode.  Have a read and see if you can answer:\n\nWhat is the big O of this function?\nHow could we improve it?\n\nfunction buildList(items) {\n  const output = [];\n  for (const item of items) {\n    const index = items.indexOf(item);\n    output.push(`Item ${index + 1}: ${item}`);\n  }\n  return output.join(\"\\n\");\n}\n\nThe problem is calling .indexOf inside the loop. The .indexOf function is an\nO(n) operation! It works by looping over the array until it finds the target\nelement, returning null if it doesn't. Calling it inside the loop makes the\noverall big O of our buildList function O(n^2)!\nTo fix this we can loop using an index. Looking up an element in an array by its\nindex is O(1), so the overall big O of the function is reduced to O(n).\nfunction buildList(items) {\n  const output = [];\n  for (let i = 0; i < items.length; i++) {\n    output.push(`Item ${i + 1}: ${items[i]}`);\n  }\n  return output.join(\"\\n\");\n}\n\nYou can also achieve the same result with JavaScript's .forEach((item, index) => {}) method on arrays, or Object.entries().\n#\nCaching intermediate results\nConsider this function to calculate the factorial of a number. A factorial in\nmathematics is written as, e.g., 5! to represent 5*4*3*2*1 or 3! to\nrepresent 3*2*1.\nfunction factorial(n) {\n  if (n === 0) {\n    return 1;\n  }\n  return n * factorial(n - 1);\n}\n\nThis function has a time complexity of O(n), but most calls to this function\nare going to redo work we've done before. Calling factorial(4) and then\nfactorial(5) will mean factorial(4) is calculated twice.\nWe can cache the result of each calculation to avoid this redundant work.\nconst cache = new Map();\nfunction factorial(n) {\n  if (cache.has(n)) {\n    return cache.get(n);\n  }\n  if (n === 0) {\n    return 1;\n  }\n  const result = n * factorial(n - 1);\n  cache.set(n, result);\n  return result;\n}\n\nThis makes use of the O(1) time complexity for lookups in a Map.  It\ndoesn't change the worst case time complexity of the factorial function, but\nit does make the average case faster at the cost of increased memory usage.\n\n#\nConclusion\nLet's recap what we've learned:\n\nBig O notation describes the relationship between a function's input and\nits wall-clock time.\nFrom slowest growth to fastest growth we saw examples of:\n\nO(1), constant time (best!)\nO(log n), logarithmic time\nO(n), linear time\nO(n^2), quadratic time\n\n\nWe can improve the time complexity of the code we write by making better\nalgorithmic choices and avoiding common pitfalls.\n\nThese posts take me a long time to write, and they wouldn't be possible without\nthe support of my family, friends, sponsors, and reviewers. I'm so grateful to\nall of you\u2014you know who you are\u2014for making it possible for me to make these.\nIf you enjoyed this post, I've written a bunch more similar that you can find on my homepage. I also love hearing from folks that have questions\nor feedback, you can reach me via email,\non Bluesky, or anonymously via\nmy silly little ping service.\nI'll leave you with one last graph comparing all of the time complexities we\ncovered in this post."
                ],
                "output": "readability/",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:04.531960+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://samwho.dev/big-o/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-24T00:49:03.152403+00:00",
                "index_texts": null,
                "output": "Big O",
                "pwd": "/data/archive/1755996525.864487",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-24T00:49:03.046806+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://samwho.dev/big-o/']' timed out after 60 seconds",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Big O",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1755996525.864487",
    "newest_archive_date": "2025-08-24T00:49:38.889002+00:00",
    "num_failures": 1,
    "num_outputs": 8,
    "oldest_archive_date": "2025-08-24T00:48:49.353412+00:00",
    "path": "/big-o/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01K3CRS5AK33DD2CF801E4A3DZ",
    "snapshot_id": "379a55d7-f0a8-4b0a-a464-39a2dc450dbf",
    "sources": [
        "/data/sources/1755996524-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1755996525.864487",
    "title": "Big O",
    "url": "https://samwho.dev/big-o/"
}