From C# and Python -- A Deep Dive into Language Execution and Typing Models

From C# and Python -- A Deep Dive into Language Execution and Typing Models

From C# and Python: A Deep Dive into Language Execution and Typing Models

One day I was asked about the difference between C# and Python. Then I realized that many people still has misunderstadings for some concepts in programming language. Starting from the question “What’s the difference between C# and Python”, I will dive into the principle of compilers and interpreters, to elaborate on this issue.

Code Execution: From Traditional to Modern Approaches

The Traditional Distinction: Compiled vs. Interpreted

Programming languages are traditionally categorized by how their code is translated and executed.

  • Compiled Languages (e.g., C, C++): A compiler translates the entire program’s source code into a standalone executable file containing machine code before it is run.1 This one-time process produces highly optimized code that is executed directly by the computer’s CPU, resulting in fast performance. However, this also means the compiled code is specific to a particular machine architecture, and every change to the source code requires a new compilation step.
  • Interpreted Languages (e.g., early JavaScript): An interpreter reads and executes the source code line by line at runtime.1 This process does not create a separate executable file. This allows for a fast development cycle, as a developer can run the code immediately after writing it.1 However, the continuous, on-the-fly translation makes interpreted languages generally slower than compiled languages.2
Feature Compiled Languages Interpreted Languages
Translation Entire program translated at once. Translated and executed line-by-line.
Execution Speed Fast, due to pre-translation. Slower, due to continuous translation.
Error Handling All syntax errors found before execution. Errors found as they are encountered.
Development Cycle Slower (edit, compile, run). Faster (edit, run).

How Compilers Translate Source Code into Executable File

Although different compilers may have different steps, we will use the most standard compiled language: C, and its most commonly taught compiler: clang for example.

1. Preprocessing

  • Tool: clang -E (if run separately)
  • Expands #include headers, macro definitions, and conditional compilation (#ifdef, #if, etc.).
  • The output is a single, pure C/C++ source file with all macros resolved.

2. Lexical Analysis (Tokenization)

  • Removes comments and whitespace

  • Converts the character stream into a stream of tokens—keywords, identifiers, literals, operators, …

3. Syntax Analysis (Parsing)

  • Builds an Abstract Syntax Tree (AST) from the token stream according to the C/C++ grammar.
  • Detects syntax errors such as a missing semicolon or mismatched braces.

4. Semantic Analysis

  • Performs type checking, scope resolution, overload resolution, and verifies language rules (e.g., correct function calls, conversions).
  • Annotates the AST with type and symbol information.

5. LLVM IR Generation

  • The validated AST is lowered into LLVM Intermediate Representation (IR)—a language-independent, SSA-based (Static Single Assignment) form.
  • This is the boundary between the front end (Clang) and the LLVM core.

6. Optimization (LLVM Middle End)

  • LLVM applies a series of target-independent optimizations to the IR:
    • Constant folding, dead-code elimination, loop transformations, inlining, vectorization, etc.

7. Target Code Generation (LLVM Back End)

  • The optimized IR is lowered to target-specific assembly (x86-64, ARM, etc.).
  • The integrated assembler converts that assembly to a relocatable object file (.o).

8. Linking

  • The system linker (ld or LLVM’s lld) combines object files and libraries, resolves symbols, and produces the final executable (e.g., hello).

How Interpreters Translate Source Code

1. Lexical Analysis

2. Syntax Analysis

3. Semantic Analysis

(4. Translate to ByteCode)

  • Some interpreter will translate AST from step 3 to bytecode (a kind of intermediate representation )

5. Interpretation

Run bytecode line by line or traverse AST

We can see that one big difference is the semantic analysis. In compiled languages, the sematic analysis is run before IR generation, while in interpreted languages, there are no such steps. In interpreted languages, there indeed will be some checking before building an ATS, but that is just to make sure the structure of the ATS is legal. For example, if there is a missing bracket or parenthesis, this will prevent the interpreter from building a legal ATS. These errors will be reported, but other errors such as typing, naming error, will not be reported unless the code is run to that line.

If you have tried to run a C# program, you will most likely use Visual Studio. After your program is completed, you will need to “build” a objective, and that is the compiling process of C#. After building, you will have an exe file that can be run immediately. Based on this characteristic, we generally categorize C# as a compiled language.

1.2 The Modern Hybrid Model: Bridging the Gap

In fact, like most modern languages, C# and Python have something more than just the standard compiled and based language.

  • The C# Hybrid Model: The C# compiler first translates source code into Intermediate Language (IL), which is a platform-independent set of instructions. When the program runs, the Common Language Runtime (CLR) uses a Just-In-Time (JIT) compiler to translate the IL into native machine code on demand. This dynamic process allows C# programs to achieve high performance while remaining portable across different operating systems and hardware.
  • The Python Hybrid Model: When a Python script is executed, the interpreter first compiles the source code into a platform-independent bytecode. A Python Virtual Machine (PVM) then executes this bytecode, translating each instruction into machine code at runtime. This process is lightweight and enables Python’s fast development cycle.

Both C# and Python use an intermediate representation and a virtual machine, but their implementation details differ. C#’s initial compilation is more thorough, enabling more powerful runtime optimizations from its JIT compiler, which contributes to its higher performance. Although technically they are all hybrid, they stiil possess the core feature that makes them compiled/interpreted languages.

What are Different Typing Categories?

Static vs. Dynamic Typing: The Timing of Type Checking

The concepts of static and dynamic typing are about when type checking occurs.

  • Static Typing: In a statically typed language, a variable’s type is checked by the compiler before the program runs. For a language like C#, you must declare the type, and it cannot change during execution. This catches type-related errors early, which is a major advantage for code reliability.

    C# Code Example:

    1
    2
    3
    4
    5
    // This works. The compiler knows 'myNumber' is an integer.
    int myNumber = 5;

    // This causes a 'compile-time' error. The compiler stops you.
    myNumber = "hello world";
  • Dynamic Typing: In a dynamically typed language, a variable’s type is checked at runtime. In Python, you do not declare a type; it is determined by the value assigned to it. This provides more flexibility but means type errors are only discovered when the code is executed.

    Python Code Example:

    Python

    1
    2
    3
    4
    5
    # This works. The interpreter knows 'myVariable' is a string.
    myVariable = "hello world"

    # This also works. The interpreter allows you to reassign the type.
    myVariable = 5

Strong vs. Weak Typing: The Rules of Conversion

The concepts of strong and weak typing relate to how a language handles conversions between different data types.

  • Strong Typing: A strongly typed language has strict rules that prevent unsafe, implicit conversions. Both C# and Python are strongly typed languages. They will stop an operation if the types are incompatible, such as trying to add a string to a number.

    1
    2
    # This will throw an exception:
    print("aaa" + 1)
  • Weak Typing: A weakly typed language is more permissive and may automatically convert data types to perform an operation, which can lead to unpredictable results.

    # This will not throw an exception
    console.log("aaa" + 1)
    

It is a common misconception that static/dynamic typing is the same as strong/weak typing. They are two separate concepts. A language can be static and strong (C#), or dynamic and strong (Python).

Chapter 3: The Relationship Between Execution and Typing

The type of a language’s execution model does not dictate its type system, and vice versa.

For example, a traditional compiled language like C is statically typed but is often considered weakly typed due to its permissive rules on implicit conversions and pointer arithmetic. Conversely, a traditionally interpreted language like Python is dynamically typed but strongly typed because it strictly enforces type compatibility at runtime.

Ultimately, the choice of a language’s execution model and type system represents a design trade-off between development speed and performance/safety.

  • Static Typing leads to a slower development cycle but provides benefits like early error detection and greater performance due to compiler optimizations.
  • Dynamic Typing allows for a faster development cycle and greater flexibility, but with the trade-off of catching errors at runtime and potential performance overhead from continuous type checks.

From C# and Python -- A Deep Dive into Language Execution and Typing Models

http://example.com/2025/09/19/A-Deep-Dive-into-Language-Execution-and-Typing-Models/

Author

John Doe

Posted on

2025-09-19

Updated on

2025-09-30

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.