Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Compile-Time vs. Run-Time Type Checking

Hello! Welcome back to our module on Type Systems.

In our previous lesson, we established the fundamental distinction between static and dynamic typing, tracing their origins to the competing design philosophies of Fortran/ALGOL (prioritizing performance and safety) and Lisp (prioritizing flexibility and expressiveness).

Today's lesson builds directly on that foundation. Our learning outcome is to contrast compile-time type checking (e.g., ML, Haskell) with run-time type checking (e.g., Lisp, Python). We will move from the "what" and "why" of typing disciplines to the "when" and "how" of their enforcement, examining the mechanics of how and when type errors are actually caught.

1. The Lifecycle of a Program: Compile Time vs. Run Time

Before we discuss type checking specifically, it's essential to have a clear mental model of the two major phases in a program's life: compile time and run time.

  • Compile Time: This is the phase where the source code you write is translated into a lower-level form that a computer can execute. This translation is done by a program called a compiler.
  • Run Time: This is the phase where the translated code is actually executed by the computer's CPU.

Type checking can occur in either of these phases, which is the core of our discussion today.

To get a clear overview of these two phases, please watch this short video.

Compile Time vs Run Time | Simply Explained

This video from TechPrep provides a simple, clear explanation of the difference between compile time and run time, which is the essential context for understanding when type checking occurs.

Watch the entire video (about 2 minutes). Pay close attention to the kinds of tasks and errors associated with each phase. Notice that 'type checking errors' are explicitly mentioned as a compile-time event.

As the video explains, compile time is about preparation and translation, while run time is about execution. This distinction directly maps to the two type-checking strategies we're exploring.

2. Compile-Time Type Checking

Compile-time type checking is the mechanism that underpins static typing. As the name implies, it is the process of verifying and enforcing type rules during the compilation phase, before the program is ever run.

Languages like ML, Haskell, Rust, and C++ use this approach. The compiler analyzes the entire program's source code, checks every function call, every variable assignment, and every operation to ensure they are consistent with the language's type rules.

If the compiler finds a type mismatch—for example, attempting to pass a string to a function that expects an integer—it will report a compile-time error and refuse to produce an executable program. The error must be fixed before the program can be built and run.

Programming Concepts: Static vs Dynamic Type Checking

This article from The Code Boss clearly defines static type checking and its consequences. It also lists the languages you'd expect, including Haskell and ML.

Read the section titled 'Static Type Checking'. Focus on the definition and the stated benefits, such as catching errors early and enabling optimizations.

Let's consider a concrete example in Haskell, a language known for its powerful compile-time checks:

-- This function explicitly states it takes an Int and returns an Int.
addOne :: Int -> Int
addOne x = x + 1

-- Somewhere else in the program...
main = print (addOne "hello") -- This line causes a compile-time error.

If you tried to compile this code, the Haskell compiler (GHC) would immediately stop and report an error similar to:
Couldn't match type '[Char]' with 'Int'.
[Char] is Haskell's type for a string. The key point is that you discover this bug without ever running the program.

3. Run-Time Type Checking

Run-time type checking is the mechanism behind dynamic typing. Here, type verification is deferred until the program is executing.

Languages like Python, Lisp, JavaScript, and Ruby use this approach. In these languages, variables themselves don't have a fixed type, but the values they hold do. The type of a value is checked on-the-fly, just before an operation is performed.

This means that a syntactically correct program might run successfully for some time, only to fail when a specific line of code is executed with an incompatible type. This failure typically manifests as a run-time error or an "exception."

Programming Concepts: Static vs Dynamic Type Checking

The same article also provides a good definition of dynamic type checking and its implications.

Now read the section 'Dynamic Type Checking'. Note the connection to features like dynamic dispatch and reflection, and the potential for run-time failures.

Let's look at the equivalent of our previous example in Python, a language you're very familiar with:

def add_one(x):
  return x + 1

# This line of code is perfectly valid from the interpreter's perspective.
# The program runs without issue up to this point.
print("Starting program...")

# The error occurs here, during execution.
result = add_one("hello") # Raises a TypeError at run-time.

print("This line is never reached.")

The Python interpreter successfully starts executing this program. The TypeError only occurs when the add_one function is actually called with the string "hello". If that line of code were in a rarely used conditional block, the error might remain hidden for a long time.

4. Contrasting the Two Approaches

We now have a clear distinction: compile-time checking is proactive, while run-time checking is reactive. Each approach has significant trade-offs that influence language design and the developer experience.

The following video provides a great visual contrast using Python (dynamic/run-time) and Go (static/compile-time).

Static vs Dynamic Typing

This Boot.dev video demonstrates the practical difference between writing code in a dynamically vs. statically typed language, highlighting how and when errors are caught.

Watch from 00:22 to 01:59. Observe how Python allows a variable's type to be changed, while Go, a statically typed language, forbids it, causing a program error before execution.

The lecture notes based on Mitchell's text provide a concise table summarizing the advantages and disadvantages, which is an excellent reference.

g Topic VI g - Types in programming languages

These lecture notes from the University of Cambridge offer a formal definition and a direct comparison of the two checking strategies.

Please read slides 6 and 7. Focus on the definitions of 'Run-time type checking' and 'Compile-time type checking' on slide 6, and then study the 'Static vs. dynamic type checking' trade-off table on slide 7.

Here is a summary of those trade-offs:

Approach Advantages Disadvantages
Compile-Time Finds errors before execution: Increases reliability. May restrict programming: Some flexible patterns are harder to express or are disallowed.
Enables optimizations: Compiler can generate faster, more efficient code. Can slow down the development cycle: The "compile-run-debug" loop can be slower than an interpreted "edit-run" loop.
Acts as documentation: Type signatures clarify intent.
Run-Time Greater flexibility: Allows for metaprogramming, reflection, and handling heterogeneous data easily. Errors occur during execution: Can lead to production failures.
Faster development cycle: No compilation step means faster iteration. Performance overhead: The runtime must constantly check types, which can slow execution.
Errors can hide in untested code paths.

5. An Important Clarification: Static/Dynamic vs. Strong/Weak

A common point of confusion is the relationship between static/dynamic typing and strong/weak typing. They are related but distinct concepts.

  • Static vs. Dynamic: Refers to when type rules are checked (compile-time vs. run-time).
  • Strong vs. Weak: Refers to how strictly type rules are enforced. Weakly typed languages often perform implicit type conversions (e.g., automatically converting the string "123" to the number 123), while strongly typed languages forbid such ambiguity.

You can have any combination:

  • Static & Strong: Haskell, Rust (very strict).
  • Static & Weak: C, C++ (allows risky conversions and pointer arithmetic).
  • Dynamic & Strong: Python, Lisp (don't convert types implicitly, e.g., 1 + "2" is a TypeError).
  • Dynamic & Weak: JavaScript, PHP (will often try to coerce types to make an operation work, e.g., 1 + "2" might result in "12").

This article provides an excellent explanation of this distinction.

Programming Concepts: Static vs Dynamic Type Checking

To refine our understanding, let's read the section of the previous article that debunks the myth of equating these two concepts.

Read the section 'Common Misconceptions', focusing on 'Myth #1: Static/Dynamic Type Checking == Strong/Weak Type Systems'. The final summary paragraph is key: one tells you when safety is enforced, the other tells you how.

Conclusion

In this lesson, we have dissected the implementation of static and dynamic typing by contrasting their enforcement mechanisms.

Key Takeaways:

  • Compile-time type checking is a static analysis performed by a compiler before a program runs. It catches type errors early, enhances reliability, and enables performance optimizations. This is the approach used by ML and Haskell.
  • Run-time type checking is a dynamic verification performed during program execution. It offers greater flexibility and is characteristic of languages like Lisp and Python, but at the cost of potential run-time failures and performance overhead.
  • The choice between them represents one of the most fundamental trade-offs in language design: early verification vs. run-time flexibility.
  • This dimension is distinct from the strong vs. weak typing axis, which concerns the strictness of type rules rather than the timing of their enforcement.

In our next lesson, we will delve deeper into the concept of type safety. Building on today's discussion, we will explore what it means for a language to be truly "type-safe" and why this is such a critical goal, comparing the strong guarantees of the ML family with the weaker guarantees of languages like C.

Can't find a good explanation? Sign up and we'll make it for you

Sign up