Delphi (12.3 Athens)

This assessment applies the coupling account — coupling as access to a shared resource, and the coupling floor [1], [2] — and the knowledge-embodiment theorem [3]; the language is assessed against its official reference [4].

1 Assessment

This page assesses Delphi in five parts: core coupling — what the language’s own constructs force; ecosystem coupling — what the surrounding platform, tooling, and community cost; engineering readiness — whether the artifact can be reproduced, verified, observed, worked in quickly, connected to other languages, and governed openly; the profile — the unscored facts a score cannot express; and the scoring detail — the mechanism marks and the arithmetic behind every score. A ✅ bullet marks a construct that helps. A ⚠️ or ❌ bullet marks a cost; the scoring model does not price every cost named here.

Core coupling 43/100 · Ecosystem coupling 84/100 · Engineering readiness 38/100

1.1 Core coupling

1.1.1 Type

  • ✅ Unit interface sections separate the declaration from the implementation: A consumer compiles against the interface part of a unit and never sees its implementation part, so either part can change without the consumer being rewritten
  • ✅ Class, record, and interface types are declared and checked at compile time: A method call, a field access, and an argument are resolved against the declared type, so no name lookup happens at run time
  • ✅ Dotted unit names give a namespace: System.SysUtils, Vcl.Forms, and the project’s unit scope names qualify every entity, so two units may declare the same identifier without a collision
  • ⚠️ A variant record is a sum type without a checked tag: A case part in a record lays alternatives over the same storage, but reading the field of the wrong alternative is not diagnosed, so the tag lives in the programmer’s discipline (T4)
  • ⚠️ Generics exist but the constraint vocabulary is small: Generic classes and records (TList<T>, TDictionary<TKey, TValue>) have been in the language since Delphi 2009, but a type parameter can be constrained only by a class, a record, a constructor, or an interface, so older library code still passes TObject and TValue
  • ⚠️ nil is a value of every class, interface, and pointer type: There is no non-nullable reference type, so a call through nil is a run-time fault rather than a compile-time error

1.1.2 Construction

  • ✅ A property separates the name from the accessor: property Count: Integer read GetCount write SetCount lets a consumer use one name while the class moves between a field and a method without touching the consumer
  • ✅ An event separates the publisher from the subscriber: property OnClick: TNotifyEvent and anonymous methods let the framework raise a notification without knowing which handler is attached, which is the observer pattern in the language
  • ✅ A class helper extends an existing type from outside it: A helper adds a method to a class declared in another unit, so an extension does not require editing the type or inheriting it
  • ✅ A constructor is an ordinary method named Create: Construction is a call rather than a special form, and Create may be virtual, so a factory returns the right subclass
  • ✅ initialization and finalization give a unit a declared start and end: The start-up and shutdown code of a unit has a named place, so a unit-level resource is not acquired in a random procedure
  • ❌ Mutability is the default: A field, a global variable, and an ordinary parameter are writable unless the declaration says const or constref, so the default reading of a variable promises nothing about it being unchanged (C2)

1.1.3 Memory

  • ❌ Nothing ties a release to the end of a block: An object from Create is released by an explicit .Free, usually inside try/finally, and no destructor runs at scope exit, so cleanup is a written obligation (M1, M3)
  • ❌ There is no ownership model: The compiler does not track which reference owns an instance, so a double .Free, a use after .Free, and a leak are neither diagnosed nor reported (M4)
  • ⚠️ Managed types are reference-counted and the rule differs by compiler: string, dynamic arrays, and interface references release their storage when the last reference goes away, but an object is reference-counted only on the mobile compilers, so the lifetime rule depends on the target
  • ⚠️ An interface reference cycle is never released: The reference count of a cycle never reaches zero and there is no tracing collector, so the cycle leaks unless a weak reference breaks it
  • ✅ TComponent.Owner gives a declared lifetime to a component graph: A component frees the components it owns when it is freed, so the object graph of a form is released deterministically
  • ✅ A managed record extends scope-tied release to a value type: Initialize, Finalize, AddRef, and RemoveRef operators let a record run code at the end of the block that holds it (Delphi 10.4 and later)

1.1.4 Error

  • ❌ Exceptions are unchecked: raise needs no declaration in the signature and no caller is obliged to handle the exception type, so the failure set of a routine is not part of its interface (Er2)
  • ✅ except with on E: T do binds the handler to a type: The handler names the exception class it catches and an unmatched exception propagates, so the recovery region is stated
  • ✅ try/finally separates cleanup from recovery: A finally block runs on both the normal exit and the exceptional one, which is where a release belongs
  • ✅ An exception object carries its own data: A class derived from Exception can carry context and raise propagates it unchanged, so a caller does not parse a message string
  • ⚠️ A bare except swallows the cause: An except block without on catches everything, including an access violation, and the recovery code may not know which failure it is covering
  • ⚠️ A memory fault arrives as an exception: A read through a dangling pointer raises EAccessViolation at the faulting instruction, so an unsafe pointer becomes a run-time error that the program may or may not handle

1.1.5 Concurrency

  • ❌ Synchronisation is manual and unchecked: System.SyncObjs supplies TCriticalSection, TMutex, TSemaphore, TEvent, and TInterlocked, but the compiler does not check the locking discipline or diagnose a race (K3)
  • ✅ System.Threading gives a task abstraction: TTask.Run, TParallel.For, and futures carry work across a thread pool, so a parallel loop is stated rather than hand-threaded
  • ✅ TThread wraps a thread as an object with a lifetime: The execution and the termination of a thread belong to one object, and Synchronize and Queue marshal a call back to another thread
  • ✅ A queued call separates a worker from the UI thread: TThread.Queue, TThread.ForceQueue, and PostMessage deliver work without a shared lock
  • ⚠️ Shared mutable state is the default: An ordinary field or global variable is reachable from every thread, and the language does not mark which state is confined to one thread (K2)
  • ⚠️ The VCL is single-threaded by contract: The GUI may be touched only from the main thread, and that rule is documented rather than enforced

1.1.6 Platform

  • ✅ One source targets Windows, macOS, iOS, Android, and Linux: The Object Pascal front end and the RTL are shared, and only the GUI framework differs between the targets
  • ✅ A second compiler implements the same language: Free Pascal and Lazarus compile Object Pascal in a Delphi-compatible mode ({$MODE DELPHI}), so a program is not bound to one vendor’s binary alone
  • ✅ The RTL is usable without the GUI framework: A console or service program links the runtime alone, so a non-GUI program does not depend on a windowing framework
  • ⚠️ FireMonkey is the cross-platform GUI path and it is second to the VCL: The component sets and the visual behaviour of the two frameworks differ, so a GUI program depends on the one it was written for
  • ⚠️ A program that calls the Windows API is bound to Windows: The Windows unit, COM, and the registry are used beside the RTL, and that part of the program is not portable
  • ⚠️ The compiler and the IDE are one vendor’s licensed product: The direction of the language follows that vendor, even though a compatible open compiler exists

1.1.7 Trade-offs (not coupling)

  • The VCL/FireMonkey choice is a GUI decision: A RAD component couples the program to the framework it was dropped onto, and the alternative is to build the user interface against the raw platform
  • Single inheritance is a deliberate simplification: The language gives interface types for the shared-behaviour case and keeps the class tree one level deep
  • The RAD model trades control for speed of construction: A designer-saved form and a live object graph are why the framework owns the lifetime of the components
  • nil is a convenience as much as a hazard: An unset reference has a natural empty value, and the discipline of an assigned check is the programmer’s
  • Pointer arithmetic is opt-in in practice: Pointer, GetMem, and System.Move exist for the low-level case, and ordinary code need not use them
  • The language is an Object Pascal dialect, not Pascal: The dialect follows from the RAD requirement, and the compatibility with Turbo Pascal is historical

1.2 Ecosystem coupling

1.2.1 Performance

  • ✅ The compiler emits native machine code: dcc32 and dcc64 produce an executable directly, so no interpreter or JIT sits between the source and the CPU
  • ✅ A desktop release does not scan the heap: With reference-counted strings, dynamic arrays, and interfaces, the runtime has no tracing collector, so there is no collection pause to plan around
  • ✅ inline, const parameters, and record passing control cost at the declaration: The call convention is stated beside the routine rather than inferred from its use
  • ⚠️ The classic optimiser is modest beside a modern back end: The vendor compiler does not apply the cross-module inlining and vectorisation that Clang or LLVM at -O2 applies, so a numerical kernel is usually delegated to a library
  • ⚠️ An interface call is an indirect call: Dispatch through an interface adds an indirection the compiler cannot resolve across units

1.2.2 Ecosystem

  • ❌ The library pool is small and centered on the vendor’s own packages: A project draws on GetIt, the bundled RTL/VCL/FireMonkey, and a small third-party set, so a missing library is often written in-house (Ec3)
  • ✅ The IDE is a complete tool: Editor, visual designer, debugger, profiler, and refactoring sit in one product, with no plugin assembly required
  • ✅ GetIt and open source cover the common needs: The IDE’s package channel, and community projects such as DUnitX, Spring4D, and ORM and JSON libraries, supply the usual layer
  • ✅ The vendor documentation is a reference work: The DocWiki holds an entry per type and per routine of the language, the RTL, and the VCL
  • ⚠️ The platform is one vendor’s commercial product: The compiler, the IDE, and the frameworks are maintained by Embarcadero, so support and direction are commercial decisions
  • ⚠️ Adoption is declining and the community is concentrated: The hiring pool and the library pool shrink with the Windows desktop market, and the active community is organized around a few sites

1.2.3 Evolution

  • ✅ Annual releases add features with a published changelog: Each 12.x release records its language and RTL additions in the release notes
  • ✅ Backward compatibility is long-lived: Turbo Pascal-era source mostly still compiles with warnings, so a codebase is rarely rewritten for the toolchain alone
  • ✅ A deprecation is reported with a replacement: The compiler warns on a deprecated routine and names its successor, so a migration is guided
  • ⚠️ The roadmap is one vendor’s: A needed language feature waits for the vendor’s release decision, and the community cannot change the language
  • ⚠️ A program that uses the VCL cannot leave Windows: The framework choice fixes the platform, and the cross-platform path is a separate framework
  • ⚠️ A project is often pinned to an older IDE: The compiler version becomes part of the codebase, and an upgrade is a project of its own

1.2.4 Domain

  • ✅ Windows desktop line-of-business software is the native target: A database front end, a form, and a grid are the idiom the framework is built for
  • ✅ Rapid UI construction is a documented strength: The visual designer, the data-aware components (TDataSet, TDataSource), and live bindings make a screen quick to build
  • ✅ A cross-platform client is supported through FireMonkey: One source produces a Windows, macOS, iOS, and Android client, although that path is secondary to the VCL
  • ✅ Native Windows integration is direct: The Windows unit, COM, and the registry are reachable from the same code as the language
  • ⚠️ The market the language fits best is now a legacy market: New Windows desktop development is a smaller share than web and service development
  • ⚠️ The fit outside a GUI application is thin: A service, a web back end, or an embedded program is written against the RTL alone, with little framework support

1.2.5 Learning

  • ✅ The curve is gentle for a Pascal or C-family developer: begin/end, a single-pass declaration order, and readable identifiers make the language quick to read
  • ✅ The IDE teaches the framework interactively: Dropping a component and reading the generated code is the standard on-ramp
  • ✅ A reader sees each name’s type at hand: var, const, and type sections and typed parameters put the declaration beside its use
  • ⚠️ The framework is a second subject beyond the language: The VCL/FireMonkey object model, the streaming of a form (.dfm), and the RTTI layer are knowledge the language does not imply
  • ⚠️ The Windows mental model is required for real work: The message loop, handles, threads, and COM are needed as soon as the program touches the system

1.2.6 Syntax

  • ✅ Keywords delimit blocks and the statements read as English: if ... then ... else, for ... do, while ... do, and begin/end
  • ✅ Identifiers are case-insensitive and the layout is free: The compiler ignores case, and the IDE’s formatter keeps the source uniform across a team
  • ⚠️ The language is verbose: Each declaration opens a var, const, or type section, and a unit needs an interface, an implementation, and a closing end. even for a few statements (S1)
  • ⚠️ try and begin pairs nest into an end ladder: An ordinary routine with two try/finally blocks ends in a run of end; lines
  • ⚠️ Attributes and compiler directives share the bracket notation: [Weak], {$IFDEF}, and {$WARN} mix the language and the compiler controls in the same text

1.2.7 Testing

  • ✅ A test framework ships with the toolchain: DUnitX, and the older DUnit, provide fixtures, attributes, and assertions, and the IDE runs the suite
  • ✅ The debugger steps through a test and shows the fault: The IDE debugger shows the call stack, the locals, and the thread state at the failure
  • ✅ Assert is in the RTL: Assert(cond) raises EAssertionFailed when assertions are enabled, so a precondition is stated in the code
  • ⚠️ The checks that would catch a fault are off by default: Range and overflow checking ({$R+}, {$Q+}) must be enabled per project, so an out-of-bounds index in a release build is undefined
  • ⚠️ Assertions are compiled out of a release build: {$ASSERTIONS OFF} removes them, so their guarantee is a debug-time one
  • ⚠️ Debugging is the vendor’s IDE’s: A command-line debug session is not the standard workflow, so a headless build server runs tests without the interactive state

1.2.8 Security

  • ⚠️ Memory safety is not guaranteed: Pointer, GetMem/FreeMem, System.Move, and PChar arithmetic are available, nothing marks the part of a program that uses them, and in that part the type system is not in the path, so its safety is the author’s claim
  • ✅ Managed types carry their length and are checked when range checking is on: {$R+} makes an out-of-range index raise ERangeError, and a string or a dynamic array knows its own length
  • ✅ There is no dynamic evaluation of source text: The language has no eval that compiles a string, and reflection reaches only published members through the RTTI, so an injection path needs the program to build the call itself
  • ⚠️ The language offers no sandbox and no capability model: A program runs with the authority of the process, and the mitigations (DEP, ASLR, control-flow guard) are linker and loader options

1.3 Engineering readiness

1.3.1 Reproducibility

  • ❌ Builds are not byte-reproducible by default.
  • ❌ Dependencies resolve without a lock by default.

1.3.2 Supply chain

  • ⚠️ Published packages carry no signed provenance by default.
  • ⚠️ The package registry is a flat name space.

1.3.3 Observability

  • ⚠️ No stable tracing and metrics standard.
  • ⚠️ The standard library has no structured logging.

1.3.4 Feedback

  • ⚠️ No fast in-editor analysis.

1.3.5 Interop

  • ⚠️ No maintained portable target.

1.3.6 Governance

  • ⚠️ One vendor decides the language’s direction.
  • ⚠️ The specification is not public.

1.4 Profile

Unscored, and kept separate from every score on purpose. The profile records what a mechanism price cannot express: what a running program costs, how well a coding model and an agent work in the language, and whether a safety-critical path exists.

1.4.1 Energy and cost

What this axis asks: what does a running program cost in energy and time?

  • ✅ Compiles to native code: the fastest class in the language energy and time benchmarks [5], [6]. A causal re-analysis attributes the difference to execution time [7].

1.4.2 AI and agent readiness

What this axis asks: how well does a coding model and an agent work in this language?

  • ❌ Sparse training data: models write it unreliably [8].

1.4.3 Safety certification

What this axis asks: is there a certified or standards-qualified path to a safety-critical system?

  • ⚠️ No certified or standards-qualified path for safety-critical systems.

1.5 Scoring detail

1.5.1 Core coupling Dimensions

Dimension Mechanism Description Present Cost
Type T1 dynamic no static check — 0
T2 weak static — 0
T3 dynamic/duck dispatch — 0
T4 no sum types ✅ −4
T5 no generics — 0
T6 no namespaces — 0
T7 type erasure/reflection — 0
T8 operator overloading — 0
T9 inheritance required for polymorphism — 0
T10 no null-safety — 0
T11 no module boundary — 0
T12 no effect tracking ✅ −8
T13 async/sync colouring — 0
T14 unchecked foreign boundary ✅ −4
Type subtotal −15 (−16)
Construction C1 global mutable state by default — 0
C2 no immutability by default ✅ −4
C3 constructors coupled to allocation — 0
C4 no constructor/validation — 0
Construction subtotal −4
Memory M1 manual deallocation ✅ −15
M2 GC non-deterministic pauses — 0
M3 no RAII ✅ −4
M4 no ownership model ✅ −8
Memory subtotal −15 (−27)
Error Er1 no structured path — 0
Er2 unchecked exceptions only ✅ −8
Er3 errors ignored/non-exhaustive — 0
Er4 no composable result type — 0
Error subtotal −8
Concurrency K1 GIL/no parallel — 0
K2 shared mutable state default ✅ −8
K3 manual sync no race detection ✅ −8
K4 no structured concurrency — 0
K5 no primitives — 0
Concurrency subtotal −15 (−16)
Platform P1 cannot function outside one vendor/domain — 0
P2 single-vendor but other uses — 0
P3 legacy/discontinued platform — 0
Platform subtotal 0

Core Dimensions score: 43/100 — total cost = −57 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.2 Ecosystem coupling Dimensions

Dimension Mechanism Description Present Cost
Performance R1 interpreted no AOT/JIT — 0
R2 GC pauses — 0
R3 JIT warmup — 0
Performance subtotal 0
Ecosystem Ec1 no package manager — 0
Ec2 fragmented tooling — 0
Ec3 small/niche/discontinued ✅ −8
Ec4 proprietary — 0
Ec5 platform-locked tooling — 0
Ec6 monoculture — 0
Ec7 legacy burden — 0
Ecosystem subtotal −8
Evolution V1 breaking changes — 0
V2 frozen — 0
V3 discontinued — 0
V4 glacial standards — 0
Evolution subtotal 0
Domain D1 domain/vendor lock-in — 0
D2 poor domain fit — 0
D3 framework forces architecture — 0
Domain subtotal 0
Learning L1 steep — 0
L2 large surface/multi-paradigm — 0
L3 sparse docs — 0
Learning subtotal 0
Syntax S1 verbose ✅ −8
S2 inconsistent — 0
S3 punctuation-heavy — 0
Syntax subtotal −8
Testing G1 weak tooling — 0
G2 poor debugging — 0
G3 runtime-only errors — 0
Testing subtotal 0
Security Q1 memory-unsafe — 0
Q2 dynamic/weak typing risk — 0
Q3 known vulnerability/injection — 0
Security subtotal 0

Meta Dimensions score: 84/100 — total cost = −16 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

1.5.3 Engineering readiness Dimensions

Dimension Mechanism Description Present Cost
Reproducibility Y1 build not reproducible ✅ −8
Y2 dependencies not pinned ✅ −8
Reproducibility subtotal −15 (−16)
Supply chain Y3 no signed provenance ✅ −8
Y4 registry without namespacing ✅ −4
Supply chain subtotal −12
Observability Y5 no stable tracing standard ✅ −8
Y6 no structured logging ✅ −4
Observability subtotal −12
Feedback Y7 slow build — 0
Y8 no fast in-editor analysis ✅ −4
Feedback subtotal −4
Interop Y9 no foreign-function interface — 0
Y10 no portable target ✅ −4
Interop subtotal −4
Governance Y11 single-vendor governance ✅ −8
Y12 closed specification ✅ −8
Governance subtotal −15 (−16)

Readiness Dimensions score: 38/100 — total cost = −62 (per-dimension subtotals are capped at −15; an uncapped cost is shown in parentheses where the cap bites).

Verdict: Delphi’s design yields a middling core score, because its separating constructs sit beside its coupling ones. The interface section of a unit, a property, an event, and an interface type each decouple a consumer from an implementation, while the single-inheritance tree is the one hierarchy the language imposes. Main strengths: native compilation, a gentle Pascal-derived syntax, and a mature IDE and framework for Windows desktop work. Main weaknesses: exceptions are unchecked, a release is written rather than tied to scope, references are nilable with no ownership check, and the platform is one vendor’s commercial product in a declining market.


The formal development. The definitions, theorems, and proofs behind this page are published in:

All published papers

References

[1]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[2]
Y. Loth, “Coupling as a consequence of cohesion: Decomposition, the coupling floor, and domain-imposed coupling.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925201.
[3]
Y. Loth, “On the nature of cohesion: Cohesion as correctness relative to a partitioning rule.” Zenodo, Jul. 2026. doi: 10.5281/zenodo.21703193.
[4]
Embarcadero, “Delphi (object pascal) documentation.” Embarcadero, 2024. Available: https://docwiki.embarcadero.com/RADStudio/
[5]
R. Pereira et al., “Energy efficiency across programming languages: How do energy, time, and memory relate?” in Proceedings of the 10th ACM SIGPLAN international conference on software language engineering (SLE), 2017, pp. 256–267. doi: 10.1145/3136014.3136031.
[6]
R. Pereira et al., “Ranking programming languages by energy efficiency,” Science of Computer Programming, 2021, doi: 10.1016/j.scico.2021.102609.
[7]
N. van Kempen, H.-J. Kwon, D. T. Nguyen, and E. D. Berger, “It’s not easy being green: On the energy efficiency of programming languages,” in Proceedings of the 40th IEEE/ACM international conference on automated software engineering (ASE), 2025. doi: 10.1109/ASE63991.2025.00131.
[8]
S. Joel, J. J. Wu, and F. H. Fard, “A survey on LLM-based code generation for low-resource and domain-specific programming languages,” ACM Transactions on Software Engineering and Methodology, 2025, doi: 10.1145/3770084.