Fact-checked by Grok 2 weeks ago

Gradual typing

Gradual typing is a in programming languages that bridges static and dynamic by allowing optional type annotations, where unannotated code behaves dynamically while annotated portions receive static checks, enabling a programmer-controlled between typing disciplines. The concept originated in 2006 with the work of Jeremy G. Siek and Walid Taha, who formalized gradual typing in their paper "Gradual Typing for Functional Languages," presenting a simply-typed extension called λ?→ that incorporates a dynamic type "?" to represent unknown types and supports structural . This foundation proved for the system and introduced principles of behavior preservation upon adding annotations, later formalized as the "gradual guarantee" ensuring that adding type annotations to a dynamically correct program preserves its observable behavior. Gradual typing addresses the trade-offs between dynamic languages, which prioritize rapid development and flexibility but risk runtime errors, and static languages, which enhance reliability and through compile-time but impose upfront burdens. Key benefits include early error detection in typed regions, optimized execution via unboxed representations for static parts, and incremental adoption that incurs dynamic checks only at type boundaries, embodying a "pay-as-you-go" cost model. Research has since refined criteria like the gradual guarantee to evaluate , distinguishing between blame-tracking semantics that isolate type errors and performance-oriented implementations. Notable implementations include Typed Racket, a dialect of Racket using higher-order contracts for sound gradual typing and supporting large-scale migration from dynamic to static code; , a superset of that employs the any type for gradual adoption and structural typing; and , a language with static type checking and a dynamic type that facilitates optional typing in web and mobile development. These systems demonstrate gradual typing's practicality in production environments, influencing languages like for and , while ongoing work explores optimizations, semantics variations (e.g., deep vs. shallow types), and empirical performance evaluations.

Core Concepts

Definition and Principles

Gradual typing is a hybrid in programming languages that integrates static type checking at with dynamic type checking at , permitting both typed and untyped to coexist within the same . This approach introduces a special type, often denoted as ? or dynamic, which represents unknown or untyped values and serves as a bridge between statically typed and dynamically typed regions. Unlike purely static systems, gradual typing does not require all to be annotated upfront, allowing programmers to control the degree of type enforcement on a per-module or per-function basis. The core principles of gradual typing emphasize incremental integration, enabling developers to gradually add type annotations to existing dynamically typed code without necessitating a complete rewrite. This supports mixed-typed programs where static checks verify type correctness in annotated sections, while runtime checks occur only at the boundaries between typed and untyped code to ensure safe interactions. Motivated by the limitations of pure systems—static typing's rigid constraints that make refactoring error-prone and dynamic typing's tendency to detect errors late at runtime—gradual typing facilitates a smooth evolution from dynamic to static typing, balancing early error detection with prototyping flexibility. In terms of basic semantics, untyped code interacts with typed code through mechanisms such as casts or proxies that enforce type contracts at interaction points, preserving without altering the behavior of purely static or dynamic subsets. These boundary checks, often implemented as dynamic casts from the type ? to specific types, catch type mismatches at the earliest possible moment while allowing untyped regions to execute freely. This "pay-as-you-go" approach ensures that the performance and safety benefits of static typing apply where annotations are present, with minimal overhead elsewhere.

Comparison with Other Typing Systems

Gradual typing occupies a hybrid position between pure static typing systems, which enforce comprehensive compile-time type checks across all code, and pure dynamic typing systems, which defer all type verification to runtime. In pure static typing, as exemplified by languages like Java, every variable and expression must be fully annotated or inferred at compile time, ensuring no runtime type errors but imposing significant upfront annotation effort and reducing flexibility for rapid prototyping. Gradual typing contrasts by allowing optional type annotations, where unannotated code (often denoted by a dynamic type like ?) is treated dynamically, enabling developers to add static checks incrementally without requiring exhaustive annotations from the outset. Pure dynamic typing, seen in languages such as , relies entirely on type checks, offering high flexibility and ease of development but prone to late error detection and potential performance overhead from ubiquitous tagging and verification. Gradual typing builds on this foundation by incorporating optional static type annotations that provide early error catching and optimization opportunities for annotated portions, without eliminating the dynamic behavior for unannotated code, thus preserving prototyping speed while enhancing safety progressively. A related but distinct approach is optional typing, where type annotations serve primarily as hints for tools like and are ignored at runtime, as in early versions of . In contrast, gradual typing enforces runtime consistency through mechanisms like casts or blame tracking, ensuring that interactions between static and dynamic code are monitored to prevent uncaught type errors, thereby providing stronger safety guarantees beyond mere documentation. Within gradual typing itself, implementations vary between sound and unsound variants. Sound gradual typing, as in Typed Racket, provably ensures no runtime type errors in well-typed programs by inserting precise dynamic checks and blame assignment, though this can introduce measurable overhead at type boundaries. Unsound gradual typing, exemplified by , prioritizes practicality by omitting some runtime enforcement, allowing potential type errors to surface at runtime for better performance and developer experience, but without formal safety proofs. Gradual typing also differs from more advanced hybrid systems like dependent types, which encode runtime properties directly into types for fine-grained verification, or flow-sensitive typing, which refines types based on . Unlike these, gradual typing focuses on a seamless blend of static and dynamic disciplines via optional annotations and consistency relations, without requiring value-dependent type expressions or path-specific refinements.

Theoretical Foundations

Consistency Relation

In gradual typing, the consistency relation, denoted \sim, serves as a core mechanism to enable safe interoperability between statically typed and dynamically typed code by relating types in a way that permits values to flow across type boundaries without immediate failure. This relation is defined such that two types are consistent if their known components match structurally, while components marked as unknown (typically denoted by the dynamic type \?) are ignored in the comparison, allowing any static type to be consistent with \?. For instance, \Int \sim \? and \String \sim \?, enabling typed values to be treated dynamically and vice versa. The relation is reflexive, meaning every type is consistent with itself (A \sim A), and symmetric, so if A \sim B then B \sim A, but it is deliberately not transitive to avoid unsoundness— for example, while \Int \sim \? and \? \sim \String, it does not follow that \Int \sim \String, as this would permit unsafe implicit conversions without checks. The relation plays a pivotal role in static type checking within gradual type systems, such as the gradually typed lambda calculus (GTLC), by replacing strict checks at boundaries between typed and untyped regions. During or checking, it allows a program to be well-typed if the argument type of a is consistent with the expected parameter type (e.g., in the application rule, if the type of T_2 \sim T_{11}, where T_{11} is the domain of the 's type). This facilitates gradual refinement, where developers can incrementally add type annotations to untyped code without breaking existing functionality, as inconsistencies are deferred to runtime rather than rejected statically. Computationally, consistency is determined through structural matching: for base types, it checks unless involving \?, which matches universally; for function types, it recursively ensures the argument types are consistent and the result types exactly (or involve \? appropriately); and for more complex types like records or unions, it aligns corresponding fields while treating unknown parts as wildcards. At runtime, the is enforced dynamically through mechanisms like insertion or objects, which monitor and validate interactions across type boundaries. For example, a from a static type to \? (an upcast) is typically safe and identity-like, while a from \? to a static type (a downcast) may fail if the dynamic value does not conform, triggering handling. This enforcement ties into the calculus, an extension of the GTLC that introduces assignment to attribute type errors precisely to the source of inconsistency. In the calculus, are labeled with sources (e.g., labels p), and failures are assigned "positive " to the dynamic side introducing the (e.g., untyped code providing an incompatible value) or "negative " to the context, ensuring that well-typed (statically precise) code is never blamed—a property formalized as the theorem or gradual guarantee. tracking thus promotes debugging by localizing faults to less-precisely typed regions, reinforcing the safety of gradual typing.

Integration with Subtyping

In gradual typing systems, traditional subtyping relations, such as width and variance rules for records or objects, apply exclusively among static types and remain unaffected by the presence of dynamic types. This preserves the standard rules from statically typed languages, where, for instance, function subtyping enforces contravariance in arguments and in return types (e.g., if \sigma_1 <: \tau_1 and \tau_2 <: \sigma_2, then \tau_1 \to \tau_2 <: \sigma_1 \to \sigma_2). The dynamic type, often denoted as ? or dyn, functions as a top type in static contexts but does not alter these rules, ensuring that subtyping judgments are decidable and confined to fully annotated components. A core principle in this integration is the separation between the consistency relation (~) and subtyping (<:), where consistency governs interactions involving dynamic types—such as allowing a dynamic value to be used where a static type is expected, subject to runtime validation—while subtyping operates solely on static types. This separation, formalized through consistent subtyping (≲), combines the two relations orthogonally: A \lesssim B holds if there exist static approximations A' and B' such that A <: A' \sim B' <: B. By design, this avoids monotonicity issues, where refining a type (e.g., replacing ? with a more precise static type) could unexpectedly break subtyping chains or introduce inconsistencies in mixed static-dynamic code. As a brief reference, the consistency relation from prior theoretical foundations ensures safe dynamic-static bridging without overlapping with static subtyping mechanics. Gradual typing extends support for polymorphism, particularly generics, by treating the dynamic type as a wildcard that can instantiate type parameters, thereby maintaining principal types in inference. For example, in a generic class like Cell<T>, substituting dyn for T (as in Cell<dyn>) allows compatibility with any concrete instantiation via a bidirectional relation, where Cell<Rectangle> . Cell<dyn> and vice versa, enabling flexible use while preserving through checks. Bounded variants, such as dyn<Shape>, further restrict wildcards to subtypes of a given upper bound, ensuring that polymorphic types remain principal and avoid over-approximation in gradual inference. This approach gradualizes subtyping polymorphism semantically, using materialization to map dynamic types to sets of static approximations, thus integrating seamlessly with Hindley-Milner-style polymorphism. In object-oriented contexts, subsumption for dynamic types permits untyped objects to be treated as subtypes of static , with checks enforcing . Optimistic subtyping allows the dynamic type to subsume any static type (dynamic ◃ C for any C), while pessimistic subtyping treats it as a supertype (dynamic ◂ ⊤), enabling dynamic objects to fulfill static contracts via nominal tags and casts at boundaries. These checks, such as verifying conformance during dispatch, maintain the gradual guarantee without wrappers, supporting features like and overloading in nominally typed gradual systems.

Historical Development

Origins and Early Research

The concept of gradual emerged as a response to the limitations of purely dynamic and static systems, drawing inspiration from earlier efforts to blend type checking approaches in functional and scripting languages. In the early , researchers explored "soft typing" as a way to approximate static type information in dynamically typed languages like without requiring full type annotations or rejecting untypable code. Robert Cartwright and Andrew Wright introduced a practical soft type system for in 1994, which inferred types conservatively and inserted runtime checks to handle ambiguities, thereby providing partial static safety while maintaining the flexibility of dynamic . This work highlighted the potential for hybrid verification in untyped environments, influencing later developments in gradual systems by demonstrating how could mitigate runtime errors without rigid enforcement. The term "gradual typing" was coined in 2006 by Jeremy Siek and Walid Taha in their foundational paper on integrating static and dynamic typing within a single language, initially applied to functional languages similar to ML. Siek and Taha proposed a type system where programmers could optionally add type annotations, with the type checker treating unannotated parts as dynamically typed via a special "?" type that deferred checks to runtime. This design allowed for incremental adoption of types, motivated by the need to retrofit static guarantees onto existing dynamic codebases, particularly in scripting languages where rapid prototyping was prioritized over upfront type declarations. Their approach addressed the growing recognition in the mid-2000s that dynamic languages like Scheme were increasingly used for large-scale applications, yet suffered from scalability issues due to unchecked errors. Building on this foundation, early formalizations of gradual typing emphasized safe interoperability between typed and untyped code through the notion of assignment. In 2006, Sam Tobin-Hochstadt and introduced interlanguage migration techniques for , enabling the gradual introduction of types into untyped modules while ensuring via contracts that track error origins. This work laid the groundwork for , a mechanism to attribute runtime type errors to either the static or dynamic side of the boundary. By 2008, Tobin-Hochstadt and Felleisen formalized the calculus in the context of Typed Racket, the first practical implementation of gradual typing, which extended PLT with optional type annotations and proved that well-typed gradual programs could not be blamed for errors originating in untyped code. Typed Racket's development from 2006 to 2008 demonstrated the feasibility of retrofitting types to dynamic languages, driven by the PLT community's interest in enhancing for evolving software systems without disrupting existing untyped libraries.

Key Milestones and Adoption

In the mid-2010s, research on gradual typing advanced significantly with the formalization of key guarantees for and behavior preservation. A seminal contribution was the 2015 paper "Refined Criteria for Gradual Typing" by Jeremy G. Siek, Michael M. Vitousek, Matteo Cimini, and John Tang Boyland, which introduced the "gradual guarantee"—a property ensuring that adding or refining type annotations in a gradually typed program does not alter its observable behavior unless a type error is detected, providing a rigorous foundation for safe between typed and untyped code. This work built on earlier criteria and became a benchmark for evaluating gradual type systems. Concurrently, efforts expanded gradual typing to object-oriented paradigms; in 2016, Ronald Garcia, Alison M. Clark, and Éric Tanter's POPL paper "Abstracting Gradual Typing" proposed a semantic using to handle gradual types in languages, enabling monotonic refinement of types while preserving program semantics and supporting features like . Industry adoption of gradual typing gained momentum during this period, integrating it into popular languages to bridge dynamic scripting with static safety. launched in 2012 as a superset of with optional static types, but its gradual typing features matured by 2015, allowing seamless mixing of typed and untyped code through structural typing and type erasure to , as demonstrated in the POPL 2015 paper "Safe & Efficient Gradual Typing for TypeScript" by Aseem Rastogi et al., which validated its usability on large codebases. Similarly, introduced in 2014 as a dialect of with gradual static typing via the Hack type checker (HackC), enabling incremental adoption on PHP's dynamic foundation while catching errors early in production-scale applications. For C#, the dynamic keyword was added in version 4.0 (2010), providing runtime type resolution akin to dynamic languages; however, it lacks the blame-tracking safety of true gradual typing systems, though later versions like 5 (2020) improved interop through features such as nullable reference types. Post-2020 developments highlighted broader integration into diverse ecosystems, particularly in scripting and game development. In Python, gradual typing support evolved through PEP 484 (accepted 2014, implemented in Python 3.5) for type hints and PEP 563, with postponed evaluation of annotations implemented as standard in Python 3.14 (released October 2025), enabling optional static checking via tools like mypy, which enforces types gradually without runtime overhead and has been adopted in major projects for refactoring large dynamic codebases. The Godot game engine enhanced gradual static typing in GDScript with Godot 4.0 (released March 2023), adding full type inference, variant handling, and performance boosts for typed code, allowing developers to mix dynamic scripting with static checks for better IDE support and error detection in game logic. Emerging experiments in Rust, a strictly static language, include crates like structural-typing (published November 2025), which introduces optional fields and gradual structural typing inspired by TypeScript, and glowstick for shape-checked tensors, signaling tentative explorations of gradual features via libraries despite Rust's core design. Research in the late and shifted toward and robustness, emphasizing techniques for large-scale systems. From 2018 onward, papers like "Migrating Gradual Types" (POPL 2018) by John Peter Campora III, Sheng Chen, Martin Erwig, and Eric Walkingshaw addressed performance in migrating untyped code to typed, showing linear scaling in type checking for programs up to millions of lines, influencing tools for industrial migration. Ongoing work from 2018–2025 has focused on , such as gradual program frameworks that combine static proofs with dynamic checks for partial specifications. Blame tolerance and error emerged as key concerns, with the 2022 ICFP paper "A Reasonably Gradual Type Theory" by Daniel Patterson and David Thrane Christiansen introducing , a dependently typed with internal and to tolerate imprecise types gracefully, reducing abrupt failures in mixed-type interactions.

Practical Implementations

Language Design Approaches

Gradual typing can be incorporated into existing dynamic languages through retrofitting, where optional type annotations are added to support both static checking and enforcement of types. This approach typically involves source-to-source translation or to insert dynamic checks at boundaries between typed and untyped code, often using mechanisms like transient casts or proxies to monitor and enforce type consistency without altering the underlying language . For instance, in , a source-to-source translator converts annotated code to standard Python 3, inserting casts to handle type transitions, which allows gradual migration but introduces trade-offs such as potential performance overhead during incremental adoption and challenges in verifying third-party untyped libraries. Migration paths must balance preserving dynamic flexibility for prototyping with the safety of static checks, often requiring phased annotation of critical modules to minimize disruption, though untyped code can propagate errors into typed sections unless bounded by explicit checks. For new languages designed with gradual typing from the outset, the type system integrates a dynamic type—such as the ? operator in early functional designs or Raku's Any type—natively to enable seamless mixing of static and dynamic code without retrofitting overhead. This ground-up support allows the dynamic type to serve as a default for unannotated elements, facilitating optional annotations while embedding consistency relations to relate static types to the unknown dynamic type. In Raku, Any acts as the root for unspecified types, promoting practical flexibility through coercions and mixins while maintaining soundness by throwing exceptions on type check failures to provide clear error feedback. Decisions on soundness vary: provably sound systems enforce full type safety via runtime casts that halt on errors, whereas practical designs like Raku's prioritize developer-friendly error reporting through exceptions to avoid silent failures. Tooling integration plays a key role in gradual typing designs, with type checkers either operating as separate tools or embedded via compiler flags to support optional static analysis. Separate checkers, such as for , run independently to infer and verify types across mixed codebases, providing fast feedback without modifying the language runtime and allowing untyped code to bypass checks entirely. In contrast, built-in approaches like Hack's compiler flags enable mode-switching between dynamic compatibility and strict static typing, integrating checks directly into the compilation process for immediate enforcement on annotated files. This distinction affects usability: separate tools offer for legacy code but require explicit invocation, while built-in flags streamline workflows in controlled environments like server-side applications. Design trade-offs in gradual typing center on balancing expressiveness with simplicity, particularly in supporting features like union types to model the dynamic unknown while managing interactions across modules and libraries. Union types enhance expressiveness by representing the ? type as a union over all static types, allowing flexible subtyping but complicating inference and check insertion due to exponential growth in type combinations. Simplicity is preserved by limiting unions to gradual contexts or using semantic subtyping to avoid overly permissive behaviors, though this can reduce the ease of migrating untyped libraries. Handling modules involves boundary checks at imports—such as contracts or proxies—to isolate typed code from untyped dependencies, trading off interoperability for soundness; for example, open-world designs assume untyped libraries may violate contracts, inserting pervasive runtime monitors to protect typed modules without requiring full retyping. These choices prioritize developer productivity in large codebases, often favoring practical unsoundness over exhaustive guarantees to accommodate evolving libraries.

Performance Considerations

Gradual typing introduces runtime overhead primarily through dynamic checks inserted at the boundaries between typed and untyped , often in the form of casts or coercions to enforce . These checks verify that values crossing the boundary conform to expected types, preventing type errors from propagating incorrectly, but they can lead to significant slowdowns in mixed programs, with early implementations like Typed Racket reporting up to 100× overhead in partially typed configurations. In sound gradual typing systems, blame computation further contributes to this cost by tracking the origin of type errors—assigning responsibility to either typed or untyped —through annotations on casts, which requires additional storage and propagation during execution. Optimization strategies mitigate these overheads by targeting redundant or predictable checks. For instance, Typed Racket leverages Racket's just-in-time (JIT) to optimize contracts generated from types at module boundaries, specializing and inlining checks where possible to reduce invocation costs in hot paths. Similarly, techniques like monotonic references enable elision of runtime checks in monomorphic, statically typed code by ensuring that once a value is verified, subsequent accesses avoid proxy wrappers, imposing no dynamic typing overhead in fully typed regions. Space-efficient coercion composition further reduces overhead by merging adjacent casts during evaluation, bounding space usage and preventing exponential growth in wrapper chains for tail-recursive programs. Compile-time impacts in gradual typing focus on for large codebases, where incremental type checking reuses prior results to avoid reanalyzing unchanged modules during iterative development or migration. This approach supports gradual adoption by minimizing feedback loops in tools like for or Pyright for . For integrating untyped libraries, type stubs provide static interfaces without runtime overhead, allowing typed code to scale by declaring expected types for library exports while deferring full checks to boundary contracts only when necessary. Empirical measurements from 2015 to 2023 benchmarks, such as the GTP suite, reveal slowdowns of 10-50% (1.1-1.5×) in mixed programs for optimized systems like Pycket and , a marked improvement from earlier 20× overheads in 2016 due to wrapper optimizations and JIT advancements. Recent 2024 research on discriminative typing introduces adaptive optimizations by inferring specialized function versions for common dynamic types, achieving up to 4× speedups over baselines like by eliding unnecessary checks in typed-dominant configurations.

Examples and Case Studies

Typed Racket and Racket

Typed Racket is an embedded, gradually typed dialect of the Racket programming language, introduced in 2008 as Typed Scheme and later renamed, which enables the incremental addition of static type annotations to dynamically typed Racket code while ensuring type safety through contract-based enforcement. This system treats untyped Racket modules as dynamically typed and inserts contracts at module boundaries to mediate interactions between typed and untyped components, allowing programs to mix both styles without requiring full rewrites. The approach draws from contract systems in Racket to implement gradual typing semantics, where type errors are caught at runtime with precise blame assignment to the originating untyped code. Key features of Typed Racket include occurrence typing, which refines types based on predicate checks to enable more precise static analysis, such as narrowing the type of a value after a conditional test like (if (number? x) ... ). Blame tracking provides detailed error messages that pinpoint the source of type violations, assigning responsibility to either typed or untyped code while ensuring that well-typed typed code cannot be blamed for errors. Additionally, it supports higher-order functions through dependent contracts and refinement types, accommodating Racket's idioms like first-class functions and continuations. In practice, Typed Racket programs begin with the declaration #lang typed/racket to activate the typed dialect, where users annotate functions, structures, and variables using a type syntax inspired by but extended for Racket's features. Mixing typed and untyped code occurs via forms like require/typed, which imports untyped identifiers with ascribed types, or by exporting typed values to untyped modules, where contracts are automatically generated to enforce gradual typing. Gradual migration is facilitated by tools such as the Typed Racket compatibility library and integrations in DrRacket, which provide type checking, error reporting, and incremental refactoring support for transitioning legacy untyped codebases. Typed Racket has served as a foundational platform for gradual typing research, influencing developments in sound type systems, performance optimization, and blame precision across multiple studies. In education, it is widely used in programming languages courses to teach type systems and semantics, leveraging Racket's pedagogical tools. For production, it underpins applications like the Redex semantic modeling tool, where typed components ensure reliability in language design experiments, and supports real-world Racket-based systems in domains such as web servers and .

TypeScript and JavaScript Ecosystems

, developed by and first publicly released on October 1, 2012, is a superset of that introduces optional static type annotations to enhance developer productivity and catch errors early during development. It allows developers to gradually add types to existing codebases without requiring a full rewrite, compiling to plain that runs in any environment. Unlike sound gradual typing systems, 's type checking is unsound, meaning it may miss certain errors at compile time, particularly when interacting with untyped , but it imposes no runtime overhead as type information is erased during compilation. Key features of TypeScript include structural typing, where compatibility between types is determined by their shape rather than nominal declarations, enabling flexible without explicit interfaces. It supports generics for creating reusable components that preserve across different data types, as well as union types that allow variables to accept multiple possible types, such as string | number. Additionally, definite assignment assertions, introduced in TypeScript 2.7, permit developers to assert that a variable will be assigned before use, helping to manage strict null checks in large codebases. Within the JavaScript ecosystem, integrates seamlessly with , the primary , allowing installation via npm install -g typescript and the use of type definitions for third-party libraries through the DefinitelyTyped repository, which provides @types packages for over 30,000 modules. Tools like support gradual adoption by linting mixed and JavaScript code, with plugins such as @typescript-eslint enforcing type-aware rules to maintain code quality during incremental migrations. As an alternative, , released by on November 18, 2014, offers a static type checker for JavaScript with bidirectional , which combines top-down checking from expected types and bottom-up inference from expressions to reduce annotation needs. TypeScript has seen widespread adoption in , powering frameworks like , which is built entirely on TypeScript to leverage its for scalable single-page applications, and serving as the primary language for , Microsoft's code editor. According to the 2023 State of JavaScript survey, approximately 41% of respondents used TypeScript for the majority of their projects, reflecting its role in large-scale projects for improved ; by the 2024 survey, 67% reported writing more TypeScript than . However, challenges arise with third-party untyped libraries, often addressed via @ts-ignore directives to suppress type errors temporarily, though this can undermine if overused; developers are encouraged to contribute or use community-provided declaration files instead.

Other Languages and Tools

, developed by for the runtime, introduces gradual typing to codebases, allowing seamless interoperation between dynamically and statically typed sections through features like strict mode for optional type enforcement. Released in 2014, it supports incremental adoption by annotating existing PHP code with types without requiring full rewrites. In C#, the dynamic keyword, introduced in version 4.0 with .NET Framework 4 in 2010, enables gradual typing by bypassing static type checks at while leveraging the Dynamic Language Runtime (DLR) for runtime resolution. This facilitates dynamic behavior, such as with ExpandoObject for flexible property addition, and has seen runtime performance enhancements in .NET 8 through optimizations like dynamic (PGO). GDScript, the scripting language for the Godot game engine, adopted optional static typing in version 3.1 released in 2018, evolving into a gradually typed system that improves code reliability and editor support. By Godot 4.0 in 2023, it matured with features like typed arrays, which enforce element types for better performance and error detection during development. Emerging gradual typing support in leverages tools like mypy and Pyright, which enable incremental type annotations on dynamically typed code without breaking existing functionality, aligning with 's type hinting system introduced in PEP 484. Mypy, an optional static type checker, supports gradual adoption by checking annotated subsets of codebases in the 2020s. Similarly, Pyright provides fast, incremental type checking for large projects. Tools like , an OCaml-based static type checker developed by , further support gradual typing in by offering responsive, incremental analysis on massive codebases, catching type errors early while accommodating untyped legacy code.

Challenges and Future Directions

Limitations and Criticisms

One major limitation of gradual typing lies in its soundness gaps, particularly in practical implementations. Many industrial gradual typing systems, such as , are intentionally unsound, employing features like bivariant for functions and arrays that permit type-unsafe code to pass static checks, potentially leading to runtime type errors. For instance, 's design prioritizes and over full soundness, allowing dynamic field access without runtime enforcement, which can result in unexpected behavior despite type annotations. Even in sound gradual typing systems, such as those based on contract monitoring like Typed Racket, the blame assignment mechanism—intended to isolate type errors to untyped code—introduces complexity, as tracking and localizing blame in mixed programs requires sophisticated runtime analysis that can be error-prone to implement correctly. Usability challenges further hinder the effectiveness of gradual typing, especially in large or legacy bases. Adding type annotations incrementally places a significant burden on developers, who must manually insert and maintain them while navigating subtle interactions between typed and untyped regions, often requiring extensive testing of annotation combinations to ensure . Migrating existing untyped to a gradually typed system exacerbates this issue, as legacy dynamic may resist annotation without refactoring, leading to incomplete type coverage and persistent type errors that undermine the benefits of gradual adoption. Performance critiques highlight the overhead inherent in gradual typing for mixed typed and untyped programs. In sound systems, type checks and wrappers at type boundaries impose substantial costs, with user reports from Typed Racket documenting slowdowns of up to 1,000× in libraries involving frequent boundary crossings, such as array operations or functional data structures. Even with optimizations like compilation, these indirections prevent straightforward performance parity with fully static or dynamic code, particularly in scenarios with high-cost types like hashtables. Adoption barriers persist due to tooling immaturity in certain languages and ongoing resistance from dynamic typing advocates. In some ecosystems, support for gradual typing remains underdeveloped, limiting features like precise or refactoring across type boundaries, which discourages widespread use beyond niche applications. Debates in the , often framed as tensions between static and dynamic paradigms, have seen proponents of pure dynamic typing criticize gradual approaches for adding unnecessary complexity without proportional gains in flexibility or productivity.

Ongoing Research and Extensions

Recent advances in verified have focused on formalizing and proving properties of type systems to ensure soundness and reliability. For instance, the work on sound verification using provides a framework that combines static and dynamic checks, proving its correctness through optimized run-time check generation and formal semantics in a simply-typed . Similarly, contract verification techniques, as explored in , enable efficient blame assignment by verifying contracts at , reducing overhead in mixed typed-untyped programs while maintaining the guarantee. These efforts, presented at conferences like POPL in 2021 and 2024, emphasize mechanized proofs using tools like Rocq to validate that untyped code cannot violate typed components' invariants. Research has also extended gradual typing to handle effect systems and concurrency, addressing challenges in tracking side effects and parallel behaviors. A key contribution is the gradual typing for effect handlers in GrEff, a language that supports incremental migration from unchecked to checked effect typing, ensuring type safety for operations like state and exceptions without full reannotation. For concurrency, extensions incorporate effect annotations to propagate guarantees across threads, as seen in gradual type-and-effect systems that use abstract interpretation to validate dynamic effect checks. These developments, detailed in ICFP 2014 and updated in 2023 publications, enable gradual adoption in concurrent settings by preserving the blame theorem for effectful interactions. Extensions to more expressive type systems include proposals for dependent types, allowing partial specification of program behaviors through propositions. The Partial Gradual Dependent Type Theory (PGTT), introduced in 2023, embeds untyped code within dependently typed contexts, supporting a smooth transition to full dependent while preserving usability for code reasoning. Additionally, AI-assisted tools for aid gradual adoption by automating annotations in dynamic languages; for example, machine learning models in LearnPerf predict performance impacts of type insertions, guiding incremental in large Python projects analyzed from GitHub repositories. These 2023-2024 innovations, including deep learning-based inference like Type4Py, facilitate scaling by reducing manual effort in . In 2025, further theoretical advancements include for gradual typing using synthetic guarded , providing a general model for non-strict languages and effectful computations (POPL 2025). Robust dynamic embedding techniques ensure space-efficient gradual typing in simply-typed languages, formalized in the Rocq proof assistant (ICFP 2025). Staged gradual typing integrates static and dynamic typing with staged computation for seamless evolution of programs. Empirical studies, such as corpus analyses of dynamic gradual types in using mypy across projects, highlight practical usage patterns. Additionally, a 2025 thesis critiques gradual typing for increasing semantic complexity without clear benefits over pure static or dynamic systems, fueling ongoing debates. Future directions emphasize standardization across language ecosystems to unify gradual typing semantics, potentially through benchmarks like the GTP suite that enable comparable performance evaluations. Integration with offers promise for cross-language gradual typing, allowing typed modules from diverse languages to interoperate safely via Wasm's , as explored in multi-language platform designs that encapsulate functions and memory for gradual guarantees. Open problems persist in reducing complexity, where static blame analysis reveals hidden data flows in dynamic types to simplify tracking without full dynamic monitoring. Scaling to massive codebases, such as GitHub-scale repositories, remains challenging due to annotation overhead and performance in tools like , necessitating optimized inference and blame mechanisms for enterprise adoption.

References

  1. [1]
    [PDF] Gradual Typing for Functional Languages
    Gradual Typing for Functional Languages. Jeremy G. Siek. University of Colorado siek@cs.colorado.edu. Walid Taha. Rice University taha@rice.edu. Abstract.
  2. [2]
    [PDF] Refined Criteria for Gradual Typing - DROPS
    5. Types for Dynamic Languages design static type systems for dynamically typed languages, such as StrongTalk [10] and Typed Racket [63].
  3. [3]
    [PDF] Is Sound Gradual Typing Dead? - Northeastern University
    In response, researchers have explored the idea of gradually-typed programming languages which allow the incremental addition of type annotations to soft- ware ...
  4. [4]
    Documentation - TypeScript for Functional Programmers
    Gradual typing. TypeScript uses the type any whenever it can't tell what the type of an expression should be. Compared to Dynamic , calling any a type is an ...Gradual typing · Structural typing · Concepts similar to Haskell · Contextual typing
  5. [5]
    Dart overview
    The Dart language is type safe; it uses static type checking to ensure that a variable's value always matches the variable's static type. Sometimes, this is ...
  6. [6]
    Safe & Efficient Gradual Typing for TypeScript | ACM SIGPLAN Notices
    We design and implement a new gradual type system, prototyped for expediency as a 'Safe' compilation mode for TypeScript. Our compiler achieves soundness by ...
  7. [7]
    Deep and shallow types for gradual languages - ACM Digital Library
    Jun 9, 2022 · This paper presents a language design that supports both deep and shallow types to utilize their complementary strengths.Abstract · Information & Contributors · Published In<|control11|><|separator|>
  8. [8]
    How to evaluate the performance of gradual type systems
    This paper presents the first method for evaluating the performance of sound gradual type systems. The method quantifies both the absolute performance of a ...<|control11|><|separator|>
  9. [9]
    [PDF] A Semantic Foundation for Sound Gradual Typing - Max S. New
    In a gradual language, statically typed and dynamically typed programs can intermingle, and dynamically typed scripts can be gradually migrated to a statically.
  10. [10]
  11. [11]
    [PDF] Safe & Efficient Gradual Typing for TypeScript
    Current proposals for adding gradual typing to JavaScript, such as Closure, TypeScript and Dart, forgo soundness to deal with issues of scale, code reuse, and ...
  12. [12]
    [PDF] A complement to blame - Informatics Homepages Server
    Contracts, gradual typing, and hybrid typing all permit less-precisely typed and more-precisely typed code to interact. Blame calculus encompasses these, and ...
  13. [13]
    [PDF] Blame for All
    It is straightforward to define an embedding d·e from the un- typed lambda calculus into the blame calculus. ... Gradual typing for functional languages.
  14. [14]
    [PDF] Consistent Subtyping for All - HKU
    Consistent subtyping is employed in some gradual type systems to validate type conversions. The original definition by Siek and Taha serves as a guideline ...
  15. [15]
    [PDF] Gradual Typing: A New Perspective - l'IRIF
    Gradual Typing: A New Perspective. 16:71. Definition B.29 (Consistency). We define the consistency relation ∼ on gradual types as follows: τ1 ∼ τ2 ⇐⇒ def.
  16. [16]
    [PDF] Gradual Typing for Generics - Computer Software Group
    Abstract. Gradual typing is a framework to combine static and dy- namic typing in a single programming language. In this pa- per, we develop a gradual type ...
  17. [17]
    [PDF] Sound Gradual Typing is Nominally Alive and Well
    Typed Racket. In Typed Racket, most of the overhead of gradual typing is caused by expensive run-time checks. Compared to Nom, these have two major causes ...
  18. [18]
    [PDF] Interlanguage Migration: From Scripts to Programs
    ABSTRACT. As scripts grow into full-fledged applications, programmers should want to port portions of their programs from script- ing languages to languages ...
  19. [19]
    [PDF] The Design and Implementation of Typed Scheme
    This paper presents Typed Scheme, an explicitly typed exten- sion of an untyped scripting language. Its type system is based on the novel notion of occurrence ...<|control11|><|separator|>
  20. [20]
    Refined Criteria for Gradual Typing - DROPS
    Apr 30, 2015 · Jeremy G. Siek and Walid Taha. Gradual typing for functional languages. In Scheme and Functional Programming Workshop, pages 81-92, September ...<|control11|><|separator|>
  21. [21]
    Abstracting gradual typing | Proceedings of the 43rd Annual ACM ...
    Abstracting Gradual Typing (AGT) is a systematic approach to designing gradually-typed languages. Languages developed using AGT automatically satisfy the ...Abstract · Information & Contributors · Published In
  22. [22]
    Hack: a new programming language for HHVM - Engineering at Meta
    Mar 20, 2014 · Hack reconciles the fast development cycle of PHP with the discipline provided by static typing, while adding many features commonly found in ...
  23. [23]
    Static typing in GDScript - Godot Docs
    With static typing, GDScript can detect more errors without even running the code. Also type hints give you and your teammates more information as you're ...How To Use Static Typing · Type Casting · Common Unsafe Operations And...
  24. [24]
  25. [25]
    A reasonably gradual type theory - ACM Digital Library
    GRIP is a reasonably gradual type theory that addresses the issues above, featuring internal precision and general exception handling. By adopting a novel ...Missing: tolerance recovery
  26. [26]
    Design and evaluation of gradual typing for python
    Gradual typing combines static and dynamic typing. Reticulated Python uses type annotations to give both static and dynamic semantics.
  27. [27]
    [PDF] Gradual Typing in an Open World - arXiv
    Oct 26, 2016 · Abstract. Gradual typing combines static and dynamic typing in the same language, offering the benefits of both to programmers.
  28. [28]
    Type system | Raku Documentation
    Any type object is a subclass of Any or Mu . Introspection methods are provided via inheritance from those base classes and the introspection metamethods (.^).
  29. [29]
    Flow, a new static type checker for JavaScript - Engineering at Meta
    Nov 18, 2014 · This enables an aggressively parallel and incremental type checking architecture, similar to Hack. This allows type checking to appear ...
  30. [30]
    Hack
    Hack is a programming language developed by Meta. It lets you write code quickly, while also having safety features built in, like static typechecking.
  31. [31]
    Gradual Typing from Theory to Practice - | SIGPLAN Blog
    Jul 12, 2019 · From its beginning 15 years ago, gradual typing is now part of everyday development practice for massive code bases at companies from Facebook ...Gradual Typing From Theory... · Typed Vs Untyped Languages · The Problem Of PerformanceMissing: primary | Show results with:primary
  32. [32]
    [PDF] Efficient Gradual Typing - arXiv
    Feb 18, 2018 · Existing implementations of gradually typed languages have not achieved this goal due to overheads associated with runtime casts. Takikawa et al ...
  33. [33]
    None
    Summary of each segment:
  34. [34]
    [PDF] Space-Efficient Gradual Typing
    Gradually-typed languages support both statically-typed and dynamically-typed code, and include runtime checks (or type casts) at the boundaries between these ...
  35. [35]
    [PDF] How to Evaluate Blame for Gradual Types - Northeastern University
    Programming language theoreticians develop blame assignment systems and prove blame theorems for gradually typed programming languages.
  36. [36]
    [PDF] Is Sound Gradual Typing Dead? - Jan Vitek
    Typed Racket compiles to Racket, which uses rather conventional JIT compilation technology. It makes no attempt to reduce the overhead of contracts or to ...<|separator|>
  37. [37]
    Monotonic References for Efficient Gradual Typing - ResearchGate
    Aug 7, 2025 · In this paper we present a new semantics called monotonic references which imposes none of the run-time overhead of dynamic typing in statically ...
  38. [38]
    Mechanical incrementalization of typing algorithms - ScienceDirect
    Aug 1, 2021 · An algorithmic schema is proposed that mechanically derives an incremental version of existing, standard typing algorithms.
  39. [39]
    [2503.08928] Toward a Corpus Study of the Dynamic Gradual Type
    Mar 11, 2025 · Gradually-typed languages feature a dynamic type that supports implicit coercions, greatly weakening the type system but making types easier to ...
  40. [40]
    Gradual Type Checking & Sorbet
    Sorbet is a gradual type checker where types are adopted incrementally, and type checks can be turned off at any level, using T.untyped.
  41. [41]
    [PDF] GTP Benchmarks for Gradual Typing Performance - Virtual Server List
    Jun 27, 2023 · The GTP Benchmark Suite is a rigorous testbed for gradual typ- ing that supports reproducible experiments. Starting from a core suite of 21 ...
  42. [42]
    Type-Based Gradual Typing Performance Optimization
    Jan 5, 2024 · Gradual typing has emerged as a promising approach to integrate both typing disciplines within a single language and harmonize their advantages ...
  43. [43]
    [PDF] The Design and Implementation of Typed Scheme
    In this paper we have demonstrated one successful approach, based on the development of a type system that accom- modates the idioms and programming styles of ...
  44. [44]
    8 Deep, Shallow, and Optional Semantics - Racket Documentation
    These types are useful for finding bugs in typed code at compile-time, but they cannot detect interaction errors at run-time. Available in: typed/racket/ ...
  45. [45]
    5 Occurrence Typing - Racket Documentation
    Occurrence typing in Typed Racket allows the type system to ascribe more precise types based on whether a predicate check succeeds or fails.Missing: blame | Show results with:blame
  46. [46]
    [PDF] Gradual Typing for First-Class Classes∗ - Northeastern University
    In this paper, we present the design of a gradual typing system that supports sound interaction between statically- and dynamically-typed units of class-based ...<|control11|><|separator|>
  47. [47]
    The Typed Racket Reference - Racket Documentation
    Typed Racket is a sister language of Racket with a static type-checker. This manual documents its types, special forms, and tools.
  48. [48]
    [PDF] Migratory Typing: Ten Years Later - Northeastern University
    Due to this anticipated cost of boundary crossings, the Typed Racket vision paper [49] argues for reasonably large units of migration. Specifically, it ...
  49. [49]
    Ten Years of TypeScript - Microsoft Developer Blogs
    Oct 1, 2022 · But this birthday is a special one – 10 years ago today, on October 1st, 2012, TypeScript was unveiled publicly for the first time. The Early ...
  50. [50]
    The TypeScript Handbook
    The TypeScript Handbook is intended to be a comprehensive document that explains TypeScript to everyday programmers.TypeScript for JavaScript... · The Basics · TypeScript for the New...
  51. [51]
    Fast and precise type checking for JavaScript - ACM Digital Library
    In this paper we present the design and implementation of Flow, a fast and precise type checker for JavaScript that is used by thousands of developers on ...
  52. [52]
    What is Angular? - Angular
    Angular is a development platform, built on TypeScript. As a platform, Angular includes: A component-based framework for building scalable web applications ...
  53. [53]
    Documentation - Do's and Don'ts
    ### Summary: Handling Third-Party Untyped Libraries in TypeScript
  54. [54]
    Performance Improvements in .NET 8 - Microsoft Developer Blogs
    Sep 13, 2023 · NET 8, the server GC now has support for a dynamic heap count, thanks to dotnet/runtime#86245, dotnet/runtime#87618, and dotnet/runtime ...
  55. [55]
    GDScript reference — Godot Engine (stable) documentation in English
    GDScript is a high-level, object-oriented, imperative, and gradually typed programming language built for Godot. It uses an indentation-based syntax similar ...GDScript · GDScript format strings · Exported properties · StringName
  56. [56]
    GDScript progress report: Feature-complete for 4.0 - Godot Engine
    Jun 2, 2021 · One of the most requested features of GDScript is now implemented: typed arrays. Now you can set the element type and let it be validated by the engine.<|control11|><|separator|>
  57. [57]
    PEP 484 – Type Hints - Python Enhancement Proposals
    Gradual typing and the full type system are explained in PEP 483. Other ... This includes support for off-line type checkers such as mypy, as well as ...
  58. [58]
    mypy - Optional Static Typing for Python
    Mypy is an optional static type checker for Python that aims to combine the benefits of dynamic (or "duck") typing and static typing.
  59. [59]
    Pyre | Pyre
    Built from the ground up to support gradual typing and deliver responsive incremental checks. Performant on large codebases with millions of lines of Python.
  60. [60]
    Migrating gradual types | Proceedings of the ACM on Programming ...
    Dec 27, 2017 · In this paper, we address this problem by developing migrational typing, which efficiently types all possible ways of adding or removing type annotations from ...Missing: usability | Show results with:usability
  61. [61]
    [PDF] How to evaluate the performance of gradual type systems
    A sound gradual type system ensures that untyped components of a program can never break the guarantees of statically typed components.
  62. [62]
    [PDF] An Argument against Gradual Type Systems in Programming ...
    May 13, 2025 · In 2006, Jeremy Siek and Walid Taha formalized the concept of gradual type systems, which integrates static and dynamic typing in a single ...