<?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: Rodrigo de Oliveira</title>
    <description>The latest articles on DEV Community by Rodrigo de Oliveira (@rodri-oliveira-dev).</description>
    <link>https://dev.to/rodri-oliveira-dev</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%2F4130418%2F87933f44-e586-43be-8232-8e13defea643.png</url>
      <title>DEV Community: Rodrigo de Oliveira</title>
      <link>https://dev.to/rodri-oliveira-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rodri-oliveira-dev"/>
    <language>en</language>
    <item>
      <title>Can Big-O Complexity Be Detected at Compile Time?</title>
      <dc:creator>Rodrigo de Oliveira</dc:creator>
      <pubDate>Thu, 17 Sep 2026 20:08:02 +0000</pubDate>
      <link>https://dev.to/rodri-oliveira-dev/can-big-o-complexity-be-detected-at-compile-time-1nk1</link>
      <guid>https://dev.to/rodri-oliveira-dev/can-big-o-complexity-be-detected-at-compile-time-1nk1</guid>
      <description>&lt;p&gt;When we learn algorithm analysis, we usually practice by looking at a piece of code and asking questions such as: how many times does this loop run? Is there another loop inside it? Does this search scan an entire collection? Does this recursion split the problem in half?&lt;/p&gt;

&lt;p&gt;After a while, we start recognizing these patterns almost automatically.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;for&lt;/code&gt; loop iterating over a collection tends to suggest O(n). Two nested loops may indicate O(n²). Binary search points us toward O(log n). An efficient sorting algorithm usually lands around O(n log n).&lt;/p&gt;

&lt;p&gt;That raises an interesting question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If we can recognize these patterns by reading code, could the compiler do the same?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly the question that led me to explore building a Roslyn analyzer capable of estimating algorithmic complexity at compile time.&lt;/p&gt;

&lt;p&gt;The short answer is: &lt;strong&gt;yes, to a certain extent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The more interesting answer is understanding where that “to a certain extent” begins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Big-O does not measure how long a method takes
&lt;/h2&gt;

&lt;p&gt;Before talking about compilers, it is worth clarifying something important.&lt;/p&gt;

&lt;p&gt;Big-O does not measure time in milliseconds.&lt;/p&gt;

&lt;p&gt;When we say an algorithm is O(n²), we are not saying it is necessarily slow. We are describing how its computational cost grows as the input size increases.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we assume &lt;code&gt;Process&lt;/code&gt; has constant cost, we have approximately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T(n) = n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add another loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T(n) = n × n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Big-O ignores constants and less relevant details so we can focus mainly on &lt;strong&gt;the rate of growth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is why it remains useful even across completely different machines.&lt;/p&gt;

&lt;p&gt;MIT, for example, introduces asymptotic complexity and recurrences early in its algorithms curriculum precisely because they provide a language for reasoning about growth without depending on specific hardware.&lt;/p&gt;

&lt;p&gt;But that abstraction also creates the first challenge for automated analysis: to infer Big-O, we need to understand not only the syntax of the code, but also &lt;strong&gt;the meaning of the operations being executed&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The compiler knows much more about our code than it seems
&lt;/h2&gt;

&lt;p&gt;This is where Roslyn makes the idea interesting.&lt;/p&gt;

&lt;p&gt;The C# compiler does not see a source file as plain text.&lt;/p&gt;

&lt;p&gt;It builds a structured representation of the program called a &lt;strong&gt;Syntax Tree&lt;/strong&gt;. The syntax tree represents declarations, expressions, loops, method calls, conditionals, and practically every other language construct.&lt;/p&gt;

&lt;p&gt;That means an analyzer does not need to search for strings such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"foreach"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can ask directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is there a &lt;code&gt;ForEachStatementSyntax&lt;/code&gt; here?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More importantly, Roslyn also provides a &lt;strong&gt;Semantic Model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That difference is significant.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking only at the text, we do not know very much.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Contains&lt;/code&gt; could belong to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;T&amp;gt;
HashSet&amp;lt;T&amp;gt;
Dictionary&amp;lt;TKey, TValue&amp;gt;
IEnumerable&amp;lt;T&amp;gt;
MyCustomCollection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And those operations may have very different costs.&lt;/p&gt;

&lt;p&gt;The Semantic Model can resolve the actual symbol referenced by the expression and reveal types, methods, arguments, and relationships between program elements. Roslyn's own documentation explains that the syntax tree alone is not enough to determine what an identifier actually refers to; that responsibility belongs to the semantic layer.&lt;/p&gt;

&lt;p&gt;It is precisely this combination of &lt;strong&gt;syntax and semantics&lt;/strong&gt; that makes much more sophisticated analysis possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  A seemingly innocent example
&lt;/h2&gt;

&lt;p&gt;Consider this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&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="n"&gt;otherItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;otherItems&lt;/code&gt; is a &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Contains&lt;/code&gt; may scan the list looking for the element.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;items&lt;/code&gt; has &lt;code&gt;n&lt;/code&gt; elements and &lt;code&gt;otherItems&lt;/code&gt; has &lt;code&gt;m&lt;/code&gt;, we can model the cost approximately as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n × m)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both collections grow at roughly the same rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now change the data structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;otherItems&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact same line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;otherItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has a very different expected algorithmic behavior.&lt;/p&gt;

&lt;p&gt;The text barely changed.&lt;/p&gt;

&lt;p&gt;The semantics changed significantly.&lt;/p&gt;

&lt;p&gt;A complexity analyzer therefore needs to resolve the symbol and recognize known operations from the libraries being used.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/rodri-oliveira-dev/complexity-analyzers" rel="noopener noreferrer"&gt;&lt;strong&gt;ComplexityAnalysis.Analyzers&lt;/strong&gt;&lt;/a&gt;, this is one of the strategies I use: known BCL and LINQ operations are identified by the &lt;strong&gt;symbol resolved by Roslyn&lt;/strong&gt;, not simply by the method name. This prevents a custom method named &lt;code&gt;Contains&lt;/code&gt; from automatically being treated as if it were &lt;code&gt;List&amp;lt;T&amp;gt;.Contains&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That detail may seem small, but it is exactly the kind of distinction that separates an interesting demo from a usable analysis tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Loops are the easy part
&lt;/h2&gt;

&lt;p&gt;Finding loops is relatively straightforward.&lt;/p&gt;

&lt;p&gt;The challenge begins when we need to understand what happens inside them.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is the complexity?&lt;/p&gt;

&lt;p&gt;We do not know.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is O(1), then we have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if &lt;code&gt;Calculate&lt;/code&gt; scans another collection of size &lt;code&gt;m&lt;/code&gt;, we may have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n × m)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or perhaps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depending on the relationship between the inputs.&lt;/p&gt;

&lt;p&gt;That is why a more sophisticated analyzer needs to move beyond purely local analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interprocedural analysis
&lt;/h2&gt;

&lt;p&gt;This is where &lt;strong&gt;interprocedural analysis&lt;/strong&gt; comes in.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&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="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Analyzing only &lt;code&gt;Process&lt;/code&gt; is not enough.&lt;/p&gt;

&lt;p&gt;We need to follow the call to &lt;code&gt;Search&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can think of it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process
    |
    +-- loop n
            |
            +-- Search
                    |
                    +-- loop m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n × m)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This kind of analysis can reveal some interesting relationships.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → B O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loop n → B O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;results approximately in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n²)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ComplexityAnalysis.Analyzers&lt;/code&gt; performs this kind of analysis within defined limits, following reachable methods and substituting called-method parameters with caller inputs when that can be done safely.&lt;/p&gt;

&lt;p&gt;But there is a very important word here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;limits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An analyzer that runs during compilation cannot explore the application's entire call graph indefinitely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The analyzer also needs to be fast
&lt;/h2&gt;

&lt;p&gt;There is an interesting irony in all of this.&lt;/p&gt;

&lt;p&gt;It would be strange to build a performance analyzer that made compilation extremely slow.&lt;/p&gt;

&lt;p&gt;Roslyn analyzers can run while we are writing code and during the build. That means analysis cost matters. Roslyn itself supports concurrent execution of analyzer actions to improve performance, provided the analyzer has been designed to operate safely in parallel.&lt;/p&gt;

&lt;p&gt;A practical tool therefore needs to enforce analysis budgets.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum call depth = 5
methods analyzed per root = 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the analysis exceeds that budget, it needs to stop.&lt;/p&gt;

&lt;p&gt;That decision matters because there is a difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;building a theoretically impressive analyzer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;building an analyzer that developers are willing to keep enabled in Visual Studio.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  And then we get to recursion
&lt;/h2&gt;

&lt;p&gt;Recursion makes things even more interesting.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&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="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&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="m"&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="p"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;Factorial&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can represent its cost with a recurrence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T(n) = T(n - 1) + O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now consider divide and conquer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T(n) = 2T(n/2) + n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying the Master Theorem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n log n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T(n) = 3T(n/2) + n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;results approximately in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n^1.585)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So an analyzer can go beyond simply counting loops. It can recognize certain recursive patterns, construct a recurrence, and attempt to solve it.&lt;/p&gt;

&lt;p&gt;In the project I have been developing, support is intentionally limited to known families: simple reduction, some exponential recurrences, forms compatible with the Master Theorem, and a restricted subset of Akra-Bazzi.&lt;/p&gt;

&lt;p&gt;The important word, once again, is &lt;strong&gt;restricted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because trying to solve every possible recurrence quickly takes us into very different territory.&lt;/p&gt;




&lt;h2&gt;
  
  
  So why can't we infer everything?
&lt;/h2&gt;

&lt;p&gt;This is where we reach the theoretical limit.&lt;/p&gt;

&lt;p&gt;It is tempting to imagine an analyzer that could receive any program and correctly answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This method is O(1).
This one is O(log n).
This one is O(n).
This one is O(n²).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For any arbitrary piece of code.&lt;/p&gt;

&lt;p&gt;That is not possible in general.&lt;/p&gt;

&lt;p&gt;Static program analysis runs into fundamental limits related to computability. The halting problem shows that there is no algorithm capable of correctly deciding, for every program and input, whether that program will terminate.&lt;/p&gt;

&lt;p&gt;More general results, such as Rice's theorem, show that non-trivial semantic properties of programs are undecidable in general. Cornell's program analysis material summarizes the practical consequence particularly well: static analyses need to work with &lt;strong&gt;approximations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That completely changes the question.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How can we determine the complexity of any program?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;we should ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“For which structures can we produce a sufficiently safe conclusion?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more productive question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sometimes “I don't know” is the best answer
&lt;/h2&gt;

&lt;p&gt;This may be the principle I like most about this kind of tool.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;ExecuteSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;ExecuteSomething&lt;/code&gt; lives in an external library the analyzer knows nothing about, there are several options.&lt;/p&gt;

&lt;p&gt;It could simply assume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that could be completely wrong.&lt;/p&gt;

&lt;p&gt;It could assume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(n)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that would also be arbitrary.&lt;/p&gt;

&lt;p&gt;A much safer alternative is to return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I don't have enough information to state the complexity.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That may sound less impressive, but it is an important characteristic of serious analysis tools.&lt;/p&gt;

&lt;p&gt;Constant false positives destroy trust.&lt;/p&gt;

&lt;p&gt;After a while, developers start ignoring the analyzer.&lt;/p&gt;

&lt;p&gt;That is why I would rather have a tool miss some cases than invent certainty.&lt;/p&gt;

&lt;p&gt;There is an interesting parallel with static analysis in general: when a property cannot be determined precisely, we need to define how we want to approximate it. Compiler and program analysis courses deal directly with this trade-off between precision, decidability, and conservatism.&lt;/p&gt;




&lt;h2&gt;
  
  
  Big-O does not replace other metrics either
&lt;/h2&gt;

&lt;p&gt;Another important point: algorithmic complexity is not the same thing as code quality.&lt;/p&gt;

&lt;p&gt;A method can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O(1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and still be almost impossible to understand.&lt;/p&gt;

&lt;p&gt;It may contain dozens of conditionals, multiple levels of nesting, too many responsibilities, and an enormous signature.&lt;/p&gt;

&lt;p&gt;That is why it makes sense to look at different metrics independently.&lt;/p&gt;

&lt;p&gt;Cyclomatic complexity attempts to represent independent control-flow paths.&lt;/p&gt;

&lt;p&gt;Cognitive Complexity attempts to approximate the effort required to understand a given flow. SonarSource created this metric specifically to address aspects of understandability that are not well represented by cyclomatic complexity alone.&lt;/p&gt;

&lt;p&gt;We can also track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nesting depth
NLOC
statement count
parameter count
token count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these metrics replaces the others.&lt;/p&gt;

&lt;p&gt;They answer different questions.&lt;/p&gt;

&lt;p&gt;Big-O essentially asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How does cost grow with the input?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cognitive Complexity asks something closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How difficult is this flow to follow mentally?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Combining those two ideas into a single score would probably create more confusion than insight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this becomes genuinely useful
&lt;/h2&gt;

&lt;p&gt;The goal of detecting complexity at compile time should not be to attach an academic label to every method.&lt;/p&gt;

&lt;p&gt;The real value appears when we can detect &lt;strong&gt;dangerous changes in algorithmic behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine someone writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customers&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="n"&gt;blockedCustomers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During development, the dataset is small and everything runs quickly.&lt;/p&gt;

&lt;p&gt;In production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers = 100,000
blockedCustomers = 80,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that data structure choice starts to matter.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;blockedCustomers&lt;/code&gt; is a list, the algorithm may perform an enormous number of comparisons.&lt;/p&gt;

&lt;p&gt;Choosing a more appropriate data structure can completely change the expected behavior.&lt;/p&gt;

&lt;p&gt;The analyzer does not need to prove the system's exact performance.&lt;/p&gt;

&lt;p&gt;It needs to be able to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“There is a linear operation inside an iteration that depends on input size. You may want to take a closer look.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That kind of feedback is valuable precisely because it happens &lt;strong&gt;before production, before benchmarking, and potentially even before code review&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static analysis does not eliminate benchmarks
&lt;/h2&gt;

&lt;p&gt;It is also important not to make a promise the tool cannot keep.&lt;/p&gt;

&lt;p&gt;An O(n) method may be slower than an O(n²) method for small inputs.&lt;/p&gt;

&lt;p&gt;Allocation, cache locality, I/O, branch prediction, concurrency, GC, databases, networks, JIT compilation, and many other factors influence real-world performance.&lt;/p&gt;

&lt;p&gt;Big-O is about asymptotic growth.&lt;/p&gt;

&lt;p&gt;Benchmarking is about concrete behavior under specific conditions.&lt;/p&gt;

&lt;p&gt;Tracing and profiling show what actually happened during execution.&lt;/p&gt;

&lt;p&gt;These tools complement each other.&lt;/p&gt;

&lt;p&gt;I like to think about it this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Static analysis
    ↓
Where might there be a problem?

Benchmark
    ↓
What is the actual cost?

Profiling / tracing
    ↓
Where is execution time actually being spent?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using one does not eliminate the need for the others.&lt;/p&gt;




&lt;h2&gt;
  
  
  The compiler as an architectural feedback tool
&lt;/h2&gt;

&lt;p&gt;Exploring Big-O through Roslyn led me to a broader conclusion.&lt;/p&gt;

&lt;p&gt;Compilers do not have to exist only to transform source code into assemblies.&lt;/p&gt;

&lt;p&gt;Roslyn turns the compiler into a platform on top of which we can build tools that understand program syntax, symbols, and semantics. Microsoft itself presents this as one of the platform's core goals.&lt;/p&gt;

&lt;p&gt;That lets us move many checks closer to the moment when a decision is made.&lt;/p&gt;

&lt;p&gt;Instead of discovering certain problems in Sonar after a push, in code review a few hours later, or in production weeks later, we can provide feedback while the developer is still writing the method.&lt;/p&gt;

&lt;p&gt;That idea is particularly interesting from an architecture perspective.&lt;/p&gt;

&lt;p&gt;Whenever an architectural rule can be expressed objectively, there is a possibility of turning it into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;analyzer
architecture test
CI rule
source generator
linter
policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a shift from passive documentation to &lt;strong&gt;executable feedback&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not every architectural decision can or should become an automated rule.&lt;/p&gt;

&lt;p&gt;But some certainly can.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned while exploring this idea
&lt;/h2&gt;

&lt;p&gt;The original question seemed simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Can Big-O be detected at compile time?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After exploring the problem, I think a better formulation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Which complexity properties can we infer safely enough to provide useful feedback to developers?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference matters.&lt;/p&gt;

&lt;p&gt;We are not trying to build an oracle.&lt;/p&gt;

&lt;p&gt;We are building a conservative analysis of a known subset of the language.&lt;/p&gt;

&lt;p&gt;Loops, known operations, LINQ, certain interprocedural calls, and some families of recurrences can produce very useful results.&lt;/p&gt;

&lt;p&gt;Dynamic code, difficult-to-resolve dispatch, unknown libraries, complex data dependencies, and structures outside the known model may remain &lt;code&gt;Unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that is fine.&lt;/p&gt;

&lt;p&gt;In tools like this, knowing &lt;strong&gt;when not to make a claim&lt;/strong&gt; is part of the quality of the analysis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;Big-O can be partially inferred at compile time because Roslyn provides much more than text: we have syntax trees, symbols, types, and semantic information.&lt;/p&gt;

&lt;p&gt;The problem becomes interesting when we move beyond counting loops and start understanding operations, method calls, and recurrences.&lt;/p&gt;

&lt;p&gt;There is no algorithm, however, that can perfectly determine semantic properties like these for every arbitrary program. Practical tools need to work with known subsets and conservative approximations.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;Unknown&lt;/code&gt; result can be far better than a fabricated estimate.&lt;/p&gt;

&lt;p&gt;And perhaps most importantly, Big-O analysis does not replace benchmarks, profiling, cyclomatic complexity, or Cognitive Complexity. Each technique answers a different question.&lt;/p&gt;

&lt;p&gt;The real gain is being able to move certain performance and design signals much closer to the moment when code is being written.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;For anyone who wants to explore the topic in more depth, I would recommend a few directions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft Learn — .NET Compiler Platform SDK / Roslyn APIs&lt;/strong&gt;: start with the Syntax Tree, Semantic Model, Symbols, and Diagnostic Analyzer APIs. The official documentation even includes a complete tutorial for building an analyzer and a code fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MIT OpenCourseWare — Introduction to Algorithms&lt;/strong&gt;: excellent material for strengthening your understanding of asymptotic analysis, recurrences, divide and conquer, and algorithm fundamentals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introduction to Algorithms — Cormen, Leiserson, Rivest, and Stein (CLRS)&lt;/strong&gt;: still one of the best references for algorithms, asymptotic analysis, and recurrences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compilers: Principles, Techniques, and Tools — Aho, Lam, Sethi, and Ullman&lt;/strong&gt;: useful for understanding compilers, intermediate representations, program analysis, and optimization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cornell Program Analysis materials&lt;/strong&gt;: particularly useful for understanding data-flow analysis, conservative approximations, undecidability, and the theoretical limits of static analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive Complexity by SonarSource&lt;/strong&gt;: a useful read for understanding why algorithmic complexity, cyclomatic complexity, and understandability are related but distinct concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to look at a practical implementation of these ideas, &lt;a href="https://github.com/rodri-oliveira-dev/complexity-analyzers" rel="noopener noreferrer"&gt;&lt;strong&gt;ComplexityAnalysis.Analyzers&lt;/strong&gt;&lt;/a&gt; applies this reasoning with Roslyn to Big-O analysis, interprocedural calls, selected forms of recursion, and complementary complexity metrics, always preferring &lt;code&gt;Unknown&lt;/code&gt; when a safe conclusion cannot be reached.&lt;/p&gt;

&lt;p&gt;In the end, perhaps the most interesting part of this exercise is not automatically determining whether a method is O(n) or O(n²).&lt;/p&gt;

&lt;p&gt;It is realizing how much knowledge is available at compile time, and how many engineering decisions we can turn into useful feedback before code ever reaches production.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>roslyn</category>
      <category>algorithms</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
