Programming Languages

Programming Languages
QR code for this page

A comparative programming-languages course covering syntax and semantics, type systems, runtime and memory models, concurrency, major paradigms, and current C, C++, Java 27, C# 14, Python 3.14, Rust, Go, Kotlin, ECMAScript, and WebAssembly lines.

A programming-languages course is not a catalogue of unrelated syntax. Its purpose is to understand the abstractions a language provides, the runtime and safety costs of those abstractions, and how language-design choices affect reasoning, verification, and long-term maintenance.

Language selection is not a popularity contest. Runtime model, type system, memory safety, concurrency, FFI/ABI needs, real-time constraints, tooling, and standardization all change project risk. Classical programming-language concepts are therefore connected to current 2026 languages and runtimes.

Unit 1: Language Design, Syntax, and Semantic Foundations

To evaluate a language's syntax, first establish which strings are admissible and then how these strings are mapped to structure. A parse tree alone does not establish program meaning: name resolution, type checking, and evaluation order require separate analysis. Running lexical analysis, parsing, and static checks on the same example program helps locate the layer at which an error arises. When a grammar is ambiguous, alternative parse trees must be examined rather than assuming a unique interpretation.

1.1. Rationale for Studying Programming Languages

Conceptual knowledge across languages provides three practical benefits:

  • model the same problem with different abstraction costs,
  • learn a new language as a new composition of familiar concepts rather than syntax memorization,
  • evaluate language/runtime trade-offs against project requirements.

Features such as async/await, pattern matching, and generics look different across languages, but their type, allocation, and control-flow semantics are the durable knowledge.

1.2. Language Evaluation Criteria

Classical criteria include readability, writability, reliability, and total cost. Critical systems add further dimensions:

  • type and memory safety,
  • determinism and worst-case latency,
  • concurrency model,
  • FFI/ABI and platform access,
  • reproducible builds,
  • toolchain and static-analysis maturity,
  • standardization and compatibility policy,
  • deployment footprint and startup,
  • library and supply-chain surface.

No criterion is universally dominant. Stronger static checks may increase compile cost; dynamic behavior can improve flexibility while moving some failures to runtime.

1.3. Language Design Is a trade-off Problem

A language is not improved merely by adding more features. Design choices interact:

implicit conversion <-> convenience / ambiguity
manual memory      <-> control / safety burden
dynamic typing     <-> flexibility / earlier error detection
GC                 <-> simpler ownership / pause + throughput cost
reflection         <-> dynamism / analyzability + AOT cost

Systems programming, data analysis, and UI scripting do not share a single optimum.

1.4. Syntax, Static Semantics, and Dynamic Semantics

Syntax defines which token structures form valid programs. Static semantics covers rules checked without executing the program, such as name resolution, type checking, or definite assignment. Dynamic semantics defines what constructs mean when executed.

int count = "forty-two"

A construct can be syntactically valid and still fail static type rules. Other failures, such as division by zero or null dereference, may depend on runtime state and language guarantees.

1.5. BNF and EBNF

Scientific animation of grammar productions expanding into a parse tree
BNF parse tree

Context-free syntax can be described using productions:

expression = term { ("+" | "-") term } ;
term       = factor { ("*" | "/") factor } ;
factor     = number | "(" expression ")" ;

The structure expresses precedence. Grammar does not by itself define typing or execution semantics.

1.6. Lexer and Parser

Deterministic finite automaton reading a binary string symbol by symbol and changing state according to its transition function
DFA string processing

A lexer turns source characters into identifiers, literals, keywords, and operators. A parser maps tokens into a syntax tree:

source -> characters -> tokens -> AST -> semantic analysis -> IR/code

For modern tooling, error recovery, source spans, and incremental parsing can be as important as accepting valid programs.

1.7. Parsing Approaches

LL1 parse table of an expression grammar and the stack trace for the input id + id
LL1 parsing

Recursive descent, LL/LR families, parser generators, and PEG-style parsers offer different grammar and maintenance characteristics. A hand-written recursive-descent parser can be excellent for a small DSL, while a larger language may benefit from generated or incremental infrastructure.

Parser choice includes diagnostics, ambiguity handling, incremental updates, and grammar maintainability, not just throughput.

Unit 2: Names, Types, and Data Semantics

2.1. Names, Binding, and Lifetime

Binding maps a name to an entity such as storage, value, function, type, or module.

Static binding can be decided at compile/link time. Dynamic dispatch selects some bindings at runtime.

Scope describes where a name is visible in source; lifetime describes how long an associated runtime entity exists. A closure can extend an environment beyond the lexical block that created it.

2.2. Lexical and Dynamic Scope

The free variable x inside f resolved lexically through the static link to the global frame giving 1 and dynamically through the caller frame g giving 2
Lexical versus dynamic scope

Most modern general-purpose languages use lexical scope: name resolution follows the static nesting of source code.

Dynamic scope follows the call chain and makes local reasoning harder. Similar dynamic-context behavior can still appear in specialized DSL or configuration systems.

Lexical scope is generally more predictable for refactoring, optimization, and static analysis.

2.3. Value, Reference, and Identity Semantics

x = y can mean a value copy, reference copy, move, or user-defined operation depending on language and type.

C struct assignment can copy values. Java object variables carry reference values. Rust may move ownership. C++ can invoke copy/move constructors or overloaded assignment.

Therefore, "pass by value" does not mean that an object itself is duplicated when the value being passed is a reference or pointer.

2.4. What a Type System Does

A type system attempts to make some invalid states or operations unrepresentable or detectable before/while they execute.

Useful dimensions include static/dynamic, nominal/structural, explicit/inferred, nullable/non-null, algebraic, and ownership/lifetime-aware types.

"Strong typing" has no single precise universal definition; a technical comparison should state which conversions and errors are actually prevented.

2.5. Nominal and Structural Typing

Nominal compatibility is based on declaration identity and explicit relationships. Java and C# class/interface relationships are largely nominal.

Structural typing accepts a value based on the members it provides. TypeScript uses this heavily; Go interfaces are also satisfied structurally without an explicit implements declaration.

Structural typing reduces adapter ceremony; nominal typing can make domain intent and boundaries more explicit.

2.6. Primitive, Product, Sum, and Optional Types

Records, structs, and tuples are product-like types: multiple components exist together. Variants/discriminated unions are sum-like types: one alternative is active.

Result<T, E> = Ok(T) | Error(E)
Option<T>    = Some(T) | None

This makes error or absence states explicit in the type system instead of relying on sentinel values. Rust Result/Option, Swift Optional, and algebraic data types in functional languages illustrate the model.

2.7. Null Is a language-design Choice

Null is convenient but broad implicit nullability expands the failure space. Languages address this differently: Kotlin separates T and T?; Swift has optionals; Rust uses Option<T>; C# provides nullable reference analysis; Java offers Optional<T> as a library abstraction for selected APIs.

The goal is not merely to ban null, but to make absence part of the contract.

2.8. Unicode and String Semantics

A user-visible character is not necessarily one byte or one code unit. Unicode code points, UTF-8/UTF-16 code units, and grapheme clusters are different abstractions.

String length can therefore mean bytes, code units, scalar values, or graphemes depending on the language/API. C/C++ byte-oriented strings, Java/C# UTF-16 heritage, and Rust UTF-8 String make different indexing choices.

Text processing must not confuse storage encoding with human-perceived characters.

2.9. Conversion and Coercion

An explicit conversion is requested by the programmer; coercion is inserted implicitly by the language. Implicit conversions improve convenience but can hide precision loss or overload ambiguity.

Narrowing conversions in critical code should be explicit and range-checked where needed. Numeric representation is part of domain correctness, not just syntax.

2.10. Generics and Parametric Polymorphism

Generics allow algorithms to be reused without losing type safety:

function max<T: Ordered>(a: T, b: T) -> T

Implementation strategies differ. C++ and Rust commonly monomorphize, Java uses erasure for many generic constructs, while .NET preserves richer generic runtime metadata and can specialize value-type instantiations.

Generic abstraction cost must be evaluated against the actual compiler/runtime model.

2.11. Forms of Polymorphism

Useful categories are:

  • ad-hoc polymorphism: overloads and operator overloading,
  • subtype polymorphism: dynamic dispatch through a base/interface,
  • parametric polymorphism: generic type parameters,
  • coercion polymorphism: implicit conversions.

Large overload sets can complicate resolution, virtual calls introduce indirection, and specialization can increase binary size.

Unit 3: Subprograms, Control, Resources, and Abstraction Models

3.1. Functions, Closures, and higher-order Programming

First-class functions can be passed and returned. A closure carries executable code together with its lexical environment.

function counter() {
  let value = 0;
  return () => ++value;
}

const next = counter();
console.log(next());
console.log(next());

The captured value remains available after the outer call returns, affecting lifetime and potentially allocation.

3.2. Parameter-passing Semantics

Parameter-passing modes giving 1, 4, 3 and 4 for a variable when two parameters alias it in the call P of a and a under value, reference, value-result and name semantics
Parameter passing modes and aliasing

Classical models include value, reference, result, value-result, and name. Modern languages expose combinations of value semantics, pointer/reference values, ref/out, borrow, and inout-style mechanisms.

Aliasing occurs when different access paths reach the same mutable object. Heavy aliasing makes optimization and local reasoning harder. Rust's borrow rules are one language-level attempt to constrain this problem.

3.3. Evaluation Order and Side Effects

An expression such as:

f(i++, i++)

is safe to reason about only when the language's operand evaluation-order rules are known. Order can be fixed, unspecified, or historically undefined depending on language/version.

Critical code benefits from explicit statements rather than dense expressions whose meaning changes with evaluation ordering.

3.4. Control Flow and Pattern Matching

Selection and iteration are fundamental, while modern languages increasingly combine data shape and control flow through pattern matching.

match result {
    Ok(value)    => use(value),
    Error(cause) => recover(cause)
}

Exhaustiveness checking can force handling of all variants and detect newly added cases at compile time.

3.5. Exceptions, Result Types, and Effect Handling

Exceptions provide a nonlocal control path and often stack unwinding. Languages differ on checked versus unchecked exceptions; Rust uses Result for recoverable errors.

Important questions are whether callers must acknowledge failure, how resources are cleaned up, whether errors are typed, and how the model composes across asynchronous boundaries.

Exceptions used as routine branching can be expensive and obscure normal control flow.

3.6. Resource Management: Manual Memory, GC, RAII, Ownership

Mark-and-sweep garbage collection marking objects reachable from the roots and sweeping an unreachable cycle and an isolated object that reference counting would not reclaim
Mark-and-sweep garbage collection

Three common approaches overlap rather than forming exclusive categories:

  • explicit allocation/free,
  • tracing garbage collection,
  • deterministic ownership/RAII.

C provides direct lifetime control but exposes use-after-free and double-free risks. Java/C# GC manages object memory but external resources still need explicit close/dispose. C++ RAII ties cleanup to object lifetime. Rust uses ownership and borrowing to target memory safety without a tracing GC.

GC does not make logical memory leaks impossible; reachable but unnecessary objects can still be retained.

3.7. Stack, Heap, and Escape Analysis

Stack versus heap is primarily an implementation concern rather than a source-level taxonomy. Escape analysis can prove that some values need not survive a local scope and optimize allocation accordingly.

Closure capture, boxing, async state machines, or reflection can change allocation behavior. Measure compiler/runtime output rather than relying on language slogans.

3.8. Object-oriented Models

Object-oriented languages combine encapsulation, abstraction, inheritance, and dynamic dispatch in different proportions. Inheritance should not be the automatic choice for code reuse.

Composition, interfaces/protocols, immutable value objects, records/data classes, sealed hierarchies, and pattern matching often provide lower-coupling alternatives.

3.9. Interfaces, Traits, and Protocols

Similar abstractions have different semantics:

  • Java/C#: interface,
  • Rust: trait,
  • Swift: protocol,
  • Go: interface.

Go interface satisfaction is structural; Rust traits can drive generic bounds and static/dynamic dispatch; Swift protocols integrate associated types and extensions. Treating them as identical hides runtime and type-system differences.

3.10. Modules, Packages, and Namespaces

Large systems need compilation and visibility boundaries in addition to types. C/C++ header/module systems, Java packages/modules, .NET assemblies/namespaces, Go packages/modules, Rust crates/modules, and JavaScript ES modules solve overlapping but distinct problems.

A package manager, language module system, and operating-system shared library are different layers.

Unit 4: Concurrency, Paradigms, and Metaprogramming

The behavior of a concurrent program cannot be inferred solely from the correctness of individual instructions. Identify shared state and the execution contexts that access it, then examine possible interleavings and visibility guarantees. Choose mutual exclusion or message passing according to the invariants that must hold. Verification must search not only for successful executions but also for schedules that produce races, deadlock, or lack of progress. Reliability means preserving the contract over permitted schedules, not merely succeeding under one accidental timing.

4.1. Concurrency versus Parallelism

Concurrency organizes the progress of multiple activities; parallelism executes work simultaneously on multiple execution resources.

Common models include OS threads with shared memory, user-mode tasks/fibers/virtual threads, async event loops, actors/message passing, CSP/channels, and data parallelism.

Race, deadlock, starvation, cancellation, and backpressure still exist; the model changes how they are expressed.

4.2. Memory Models and happens-before

Scientific animation of program order and synchronization establishing a happens-before relation
Happens-before memory model

Shared-memory behavior is constrained by the language memory model, not merely by CPU cache details. The memory model defines which reorderings are legal and what synchronization establishes visibility.

Atomics, volatile accesses, mutexes, and channels are not interchangeable. Their ordering and exclusion guarantees differ by language.

4.3. What async/await Actually Does

async/await usually expresses continuation/state-machine control flow. It does not guarantee parallel execution or a new thread.

It is highly useful for I/O concurrency. CPU-bound work still needs an explicit scheduling or parallelism decision.

Cancellation and timeout should be structured; otherwise asynchronous systems can accumulate orphaned work and resource leaks.

4.4. Functional Programming

Functional programming emphasizes first-class functions, immutable data, and expression composition. A pure function has no externally visible side effect and returns the same output for the same input.

Real software performs I/O, so the engineering goal is often to make effect boundaries explicit rather than eliminate every effect.

Java Streams, C# LINQ, Kotlin collection operators, and JavaScript higher-order array methods bring functional concepts into multi-paradigm languages.

4.5. Logic and Declarative Programming

Logic programming describes relations and rules rather than a fixed sequence of commands; Prolog is the classic example.

SQL is also declarative in an important sense: the programmer specifies a desired relational result while the optimizer chooses a physical execution plan.

Declarative syntax does not remove performance concerns; it moves execution strategy beneath the abstraction.

4.6. Metaprogramming, Macros, and Reflection

Metaprogramming lets programs work on program structure. Textual preprocessors, hygienic macros, annotation processors, source generators, compile-time evaluation, and runtime reflection have very different safety and timing properties.

Runtime reflection can conflict with aggressive trimming or AOT. Compile-time generation shifts work into the build pipeline and can make runtime behavior more explicit.

Unit 5: Compilation, Runtime, and Language Boundaries

5.1. Compiler, Interpreter, JIT, and AOT

The binary distinction between "compiled" and "interpreted" languages is too weak for modern runtimes:

Java source -> bytecode -> JVM interpreter/JIT -> native code
C# source   -> IL       -> CLR JIT/AOT        -> native code
C/C++       -> object   -> linker             -> native image
JavaScript  -> parser/IR/interpreter/JIT       -> native execution

AOT changes startup and deployment characteristics; JIT can optimize using runtime profiles. The right trade-off is workload-specific.

5.2. Intermediate Representation and Optimization

Compilers usually pass through one or more intermediate representations. IR enables type lowering, SSA transforms, inlining, dead-code elimination, vectorization, and register allocation.

LLVM IR is a well-known compiler IR. JVM bytecode and .NET IL also serve as distributable runtime formats.

5.3. ABI, FFI, and Language Boundaries

Cross-language calls require agreement on calling convention, symbol names, layout/alignment, ownership, exception behavior, and string representation.

A C ABI is a common low-level interoperability boundary. Rust unsafe, Java native interfaces/FFM, and .NET P/Invoke explicitly mark places where higher-level language guarantees can weaken.

A serialized process boundary is different from same-process FFI and introduces different latency and failure semantics.

5.4. WebAssembly as a Target Platform

The W3C WebAssembly Core Specification dated 12 August 2026 describes release 3.0 and is published as a Candidate Recommendation Draft. WebAssembly is a safe, portable, low-level code format that C/C++, Rust, and other languages can target for browsers or WASI environments.

Wasm is not a source-language replacement; it is a compilation and runtime target with its own host-interface, ABI, and interoperability decisions.

Unit 6: Modern Languages, Ecosystems, and Application Domains

6.1. C and C++ Current Standards

The current published ISO standards are ISO/IEC 9899:2024 for C and ISO/IEC 14882:2024 for C++.

C provides explicit memory layout and pointers with a small runtime abstraction surface, which also exposes undefined behavior and manual lifetime risks. C++ adds RAII, templates, constexpr, concepts, and a broad standard library with a zero-overhead abstraction philosophy.

See C Programming and C++ Programming.

6.2. Java and the JVM

Java 27 is the current feature release; Java 25 remains the current long-term-support (LTS) release. Java source compiles to JVM bytecode; runtimes can combine interpretation, JIT/AOT compilation, and multiple garbage collectors.

Nominal static typing, generics, records/sealed classes, pattern matching, virtual threads, and a managed runtime make Java a useful case study for language/runtime co-design.

See Java Programming.

6.3. C# and .NET

C# 14 is the current language release on .NET 10. It combines nominal static typing, generics, delegates, LINQ, records, pattern matching, async/await, spans, and explicit unsafe escape hatches.

CLR IL, metadata, JIT, and AOT choices expose many implementation trade-offs directly.

See C# Programming.

6.4. Python 3.14

Python 3.14.7 is the current maintenance release in the 3.14 line. Python 3.14 continues a dynamic object model with first-class functions, generators/coroutines, and extensive runtime introspection. The 3.14 line officially supports free-threaded Python, so "every Python build always has the same GIL constraint" is no longer a sound universal statement.

Type annotations are a separate analysis layer; they do not by themselves turn every runtime operation into statically enforced typing.

6.5. Rust 2024 Edition

Rust 1.98.1 is the current stable release, while Rust 2024 identifies the current edition line rather than a compiler version. Rust's distinctive design uses ownership, borrowing, and lifetime analysis to provide memory-safety guarantees without a tracing GC.

unsafe marks a boundary where some proofs are delegated to the programmer; it does not disable safety checking for the entire program.

Rust enums, pattern matching, and Result are strong examples of sum types and explicit errors.

6.6. Go 1.27

The Go 1.27 specification defines a strongly typed, garbage-collected general-purpose language with explicit support for concurrency. Go 1.27 also adds generic methods.

Goroutines and channels provide a CSP-influenced concurrency model, while mutexes and atomics remain available when shared memory is appropriate.

Go's deliberately small language surface and compatibility policy represent a different trade-off between expressivity and ecosystem stability.

6.7. Kotlin 2.4 and Swift

Kotlin 2.4.20 is the current release on the 2.4 line and targets JVM, JavaScript, WebAssembly, and Native environments. Null safety, extensions, coroutines, and multiplatform compilation illustrate the difference between a source language and its execution targets.

Swift's official language reference covers protocols, value semantics, generics, optionals, and structured concurrency. It is useful as a modern type-system case study beyond its common association with Apple UI development.

6.8. JavaScript / ECMAScript 2026

ECMAScript 2026 is the 17th edition of ECMA-262. JavaScript combines dynamic typing, a prototype object model, closures, promises/async control flow, and modules.

The JavaScript language and browser Web APIs are different standards. fetch, DOM, and localStorage are host/browser APIs, not the ECMAScript language itself. Node.js provides another host environment.

6.9. A Practical language-selection Matrix

Instead of selecting a universal winner, make constraints explicit:

Criterion              Question
---------------------  ---------------------------------------------
Latency                Are p99/p999 or worst-case limits strict?
Memory safety          How large is the unsafe/native boundary?
Runtime                Is GC/JIT acceptable?
Startup/footprint      Is the process short-lived or edge-deployed?
Concurrency            Threads, actors, async, or channels?
Interop                 Must it call existing C/JVM/.NET/native code?
Tooling                 Are analyzers/profilers/debuggers mature?
Portability            Which OS/CPU/runtime targets exist?
Team                    What expertise and maintenance horizon exist?
Standardization         How stable is the language/toolchain policy?

The matrix documents trade-offs; it does not produce a context-free ranking.

6.10. Critical and real-time Systems

Average throughput is not enough for critical workloads. Allocation, GC pauses, hidden synchronization, exception paths, scheduler behavior, and JIT warm-up can affect tail latency.

For deterministic requirements, constrain allocation, identify blocking points, measure synchronization, benchmark error paths, pin compiler/runtime versions, analyze undefined behavior and data races, and test with production-like workloads.

A language choice does not replace operational engineering discipline.

6.11. Security and Language Choice

A memory-safe language does not fix authorization, injection, or business-logic flaws, but bounds checks, ownership, safe references, and type-safe serialization can reduce specific exploit classes.

FFI, unsafe, reflection, deserialization, and code generation reopen trust boundaries and should be explicitly included in the threat model.

Supply-chain risk often comes from package ecosystems and build pipelines rather than language syntax itself.

Unit 7: Language Selection and Technical Evidence

For concrete implementations see C Programming, C++ Programming, Java Programming, and C# Programming. For grammar and formal languages see Automata Theory and Formal Languages; for runtime memory see Operating Systems; and for the browser-host model see Web Programming.

7.2. Relating Language Choice to the Execution Model

Comparing programming languages by syntax alone is misleading. The same algorithm can have very different operational behavior depending on memory management, calling conventions, error contracts, concurrency primitives, and runtime architecture. The useful question is therefore not simply "which language is faster?" but which costs are visible, controllable, and measurable.

A garbage-collected runtime can reduce ownership bookkeeping for short-lived objects, while a latency-sensitive system may still need explicit allocation and pause analysis. An ownership-oriented language can move some lifetime errors to compile time, but API design must then express ownership and borrowing more precisely.

A useful evaluation matrix considers type safety, error and cancellation contracts, memory ownership, concurrency, ABI/FFI boundaries, compilation and deployment models, and the quality of diagnostics and profiling tools. This treats a language as an engineering system rather than a list of features.

7.3. Tying Language Claims to Technical Evidence

A language feature should be evaluated at three distinct levels: the language specification, the observed behavior of a concrete compiler/runtime, and measurements from a defined workload. A claim that “the language guarantees” something should be supported by the specification; behavior observed in one implementation is not automatically a language contract.

Performance comparisons require the same algorithm, representative data, comparable optimization settings, and a reproducible measurement procedure. Startup, warm-up, allocation, and I/O costs should be reported separately, together with runtime version and hardware.

A language choice should also state its constraints: real-time requirements, ABI/FFI needs, deployment model, fault tolerance, team skills, and maintenance horizon. This separates engineering evidence from syntax preference.

Unit 8: Advanced Semantics and Programming Models

8.1. Binding Time: from Language Design to Runtime

Binding connects a name, symbol, or program entity to one of its properties. A useful model tracks when each decision becomes fixed.

language design time
       |
       v
implementation time
       |
       v
compile time
       |
       v
link / load time
       |
       v
runtime

An operator's meaning belongs to language design. A floating-point representation is shared between specification and implementation. A local type can be resolved at compile time while a virtual call target can be selected at runtime.

Earlier binding may expose more information to verification and optimization; later binding preserves runtime flexibility. Cost depends on both semantics and implementation.

8.1.1. Binding Time Is Not a Performance Label

Runtime A:
full lookup on every call

Runtime B:
resolve once
 -> cache
 -> fast path

JIT compilation, inline caches, and runtime profiling can make dynamic semantics inexpensive on common paths.

8.1.2. Binding and Deployment

A compile-time constant can require rebuild when changed. Configuration loaded at runtime can change without rebuilding.

earlier binding
 -> more prior information
 -> less runtime flexibility

later binding
 -> more runtime flexibility
 -> more runtime resolution

8.2. A Variable Is More Than a Name and a Value

Separate:

name
address / storage
value
type
scope
lifetime
mutability
ownership

Classical terminology distinguishes an l-value, referring to storage, from an r-value, used as a value. Modern languages refine these categories, but reading a value and mutating a storage location remain different questions.

8.2.1. Scope versus Lifetime

source visibility -> scope
runtime existence -> lifetime

A closure can keep an outer variable alive after its lexical call has returned.

8.2.2. Storage-duration Categories

Common classes include static/program-lifetime storage, automatic call-frame storage, dynamically allocated heap objects, and task/thread-local storage.

GC, RAII, and ownership are different answers to the same lifecycle questions: when is an object created, who can reach it, who cleans it up, and when does access become invalid?

8.2.3. Aliasing

A ----\
       >---- same mutable object
B ----/

Aliasing weakens local reasoning. Alias analysis matters to low-level optimization, while ownership/borrow systems can reject particular alias-plus-mutation patterns statically.

8.3. Type Equivalence, Compatibility, Inference, and Variance

“Statically typed” is not a complete description. Ask:

  1. When is the type determined?
  2. When are two types equivalent?
  3. Which conversions are permitted?
  4. How does subtyping propagate through compound types?

8.3.1. Nominal and Structural Equivalence

A = {x: int, y: int}
B = {x: int, y: int}

A nominal system can keep A and B distinct. A structural system can accept them by shape.

Nominal typing strengthens domain boundaries; structural typing can improve composition.

8.3.2. Type Inference Is Not Dynamic Typing

static type system
       +
compiler inference
       =
static validation without spelling every type

8.3.3. Replace “Strong Typing” with Concrete Questions

The phrase has no universal metric. Better ask about implicit conversions, representation safety, error detection time, unsafe escape hatches, and optional/null modeling.

8.3.4. Variance

If:

Cat <: Animal

it does not automatically follow that:

Box<Cat> <: Box<Animal>

Covariance, contravariance, and invariance encode safety constraints in generic APIs.

8.4. Arrays, Slices, Records, Unions, and Algebraic Data

8.4.1. Array versus Slice

buffer: [a b c d e f g h]
           |-------|
             slice

A subarray operation may copy data or return a view. That changes lifetime and performance semantics.

8.4.2. Row-major and column-major Layout

row-major:
[a00 a01 a02 a10 a11 a12]

column-major:
[a00 a10 a01 a11 a02 a12]

This matters at cache, numerical-library, and FFI boundaries.

8.4.3. Records / Structs

A record groups heterogeneous fields. Layout, alignment, and padding may become observable at ABI, binary-file, and protocol boundaries.

8.4.4. Untagged versus Tagged Unions

A C-style union may require an external convention for the active member. A discriminated union / sum type carries the variant:

Result
 +-- Success(value)
 +-- Error(reason)

This can make invalid states harder to represent.

8.5. Expression Semantics: Order, short-circuiting, Overflow, and Undefined Behavior

8.5.1. Evaluation Order

f(g(), h())

Which call happens first? Side effects make the answer observable.

8.5.2. Short-circuiting

x != 0 && 10 / x > 2

depends on the second expression being skipped when the first is false. This is a control-flow guarantee.

8.5.3. Overflow

Integer overflow can wrap, trap, depend on execution mode, or be undefined. Bit width alone does not define arithmetic semantics.

8.5.4. Undefined Behavior

In some systems languages, the standard deliberately leaves certain invalid operations outside the language contract. Optimizers can reason under the assumption that such operations never occur.

See C Programming Fundamentals and Object-Oriented Programming with C++ for concrete memory examples.

8.6. Subprogram Runtime: Activation Records, Recursion, and Closures

A function call is a runtime protocol.

activation record
 +-- return address
 +-- parameters
 +-- locals
 +-- temporaries
 +-- saved registers
 +-- exception / GC metadata

Actual placement depends on ABI and optimization.

8.6.1. Recursion

The call tree of naive recursive Fibonacci with 15 calls for fib 5 and 21891 for fib 20 and the linear number of calls after memoization
Fibonacci call tree and memoization
f(3)
 -> f(2)
    -> f(1)
       -> f(0)

Each active call needs its own logical state.

8.6.2. Parameter-passing Semantics

Classical models include value, reference, result, value-result, and name. Modern syntax differs, but useful questions remain:

is the value copied?
is object identity shared?
can the callee rebind caller storage?
does mutation affect the caller?
how is lifetime constrained?

8.6.3. Closures

Closure capture and environment lifetime with computed steps and state transitions
Closure capture and environment lifetime
function code
     +
captured lexical environment

A closure changes lifetime and allocation behavior, not just callback syntax.

8.6.4. Coroutines and Generators

start
 -> yield / suspend
 -> resume
 -> yield
 -> finish

A coroutine is not automatically an OS thread. async/await can compile to resumable state machines.

8.7. Abstract Data Types, Modules, and Encapsulation Boundaries

An Abstract Data Type defines operations and invariants rather than exposing representation.

Visible:
push(x)
pop()
isEmpty()

Hidden:
array / list / other representation

8.7.1. Module Is Not Class

Modules can provide namespace, visibility, dependency, and compilation boundaries without inheritance or object identity.

8.7.2. Representation Exposure

public API
   |
   +--> mutable reference to internal state

can break encapsulation regardless of private keywords.

8.7.3. Abstraction Cost

Virtual calls, allocation, checks, or wrappers can have costs; optimizers can erase some of them. “High level is slow” and “zero-cost means literally free” are both too broad without measurement.

8.8. Non-local Control: Exceptions, Cleanup, Cancellation, and Effects

A()
 -> B()
    -> C()
       -> throw
             |
             +----> handler

RAII, try/finally, defer, using, and context managers address the same cleanup problem.

8.8.1. Exception versus Result

exception -> non-local control transfer
Result    -> explicit value flow

8.8.2. Cancellation

Asynchronous cancellation must define what happens to locks, transactions, partial output, and child tasks. Structured concurrency attempts to make child-task lifetime follow a visible scope.

8.9. Concurrency Models: Shared Memory, Channels, Actors, and Structure

8.9.1. Shared Memory

Thread A ----\
              >---- shared mutable state
Thread B ----/
        lock / atomic / condition

8.9.2. Monitors

A monitor groups shared state, operations, and synchronization conditions.

8.9.3. Channels / CSP-style Communication

Task A --message--> Channel --message--> Task B

8.9.4. Actors

Actors combine private state, mailbox, and message processing.

8.9.5. Structured Concurrency

parent task
  +-- child A
  +-- child B

The parent scope accounts for child lifetime rather than losing detached work.

8.9.6. Memory Model

Language/runtime rules define legal reordering and synchronization/happens-before relations. See Operating Systems and Computer Architecture.

8.10. Functional Programming Is More Than Using Functions

8.10.1. Immutability

old state
   |
function
   |
   v
new state

Persistent structures can share unchanged representation.

8.10.2. Strict and Lazy Evaluation

expression
 -> suspended computation
 -> evaluate on demand

Laziness helps express streams but can complicate memory and performance reasoning.

8.10.3. Pure Core and Effect Boundary

pure core
   |
   +--> I/O
   +--> database
   +--> network

8.10.4. Tail Calls

Tail recursion is only stack-safe when the language/runtime provides the required optimization or execution semantics.

8.11. Logic and Constraint Programming in More Depth

Logic Programming and Prolog covers the paradigm in detail.

8.11.1. Unification and Backtracking

query
 -> rule 1
    -> fail
 -> rule 2
    -> subgoals
    -> success

Declarative meaning and concrete search strategy are not identical.

8.11.2. Tabling

Caching subgoal results can reduce repeated work and improve termination for some recursive relations.

8.11.3. Constraint Programming

variables
+
domains
+
constraints
+
search

describes the solution space rather than spelling out every solver step.

8.12. Formal Semantics: Mathematical Meaning for Programs

8.12.1. Operational Semantics

<expression, state>
      |
      v
<expression', state'>

8.12.2. Axiomatic Semantics

{ P } program { Q }

supports reasoning about invariants and correctness.

8.12.3. Denotational Semantics

syntax construct
      |
 semantic mapping
      |
      v
mathematical meaning

These ideas connect programming languages with Automata Theory and Formal Languages, static analysis, and verification.

8.13. From Language Semantics to Memory Models

Correct multicore programming is more than the absence of a data race. At the programming-language level, concurrency semantics require three distinct questions: which legal sequential execution can explain the observed result, under what conditions a thread is guaranteed to make progress, and which synchronization events establish memory visibility; lock contention is a separate performance phenomenon again. Linearizability provides a strong correctness model, while lock-free and wait-free progress classes show why latency cannot be explained merely by counting mutexes.

  • Evaluate compiler optimizations against the language-defined observable behavior.
  • Separate the language memory model from hardware reordering in concurrent programs.
  • Keep the language abstract machine distinct from the runtime cost model.

Unit 9: Runtime, Evolution, Security, and Suitability

9.1. Beyond the compiled/interpreted Binary

source
  |
  +--> native AOT ----------------------> machine code
  |
  +--> bytecode / IR --> interpreter ---> execution
  |                    |
  |                    +--> JIT --------> machine code
  |
  +--> adaptive interpreter
  |      |
  |      +--> specialize hot operations
  |
  +--> mixed JIT + AOT systems

Performance claims should identify the implementation path before the language name.

JITs can specialize hot methods and common runtime types, then deoptimize when assumptions fail. Adaptive interpreters can also specialize bytecode without becoming a conventional JIT. AOT can improve startup and deployment while giving up or relocating some runtime-profile opportunities.

language name != one execution model

9.2. Polyglot Systems: Control Planes and Native Kernels

Python
  |
  | orchestration / API
  v
C / C++ native library
  |
  +--> SIMD
  +--> GPU kernel
  +--> vendor library

System performance can therefore differ radically from interpreter-loop performance.

FFI boundaries must define ownership, allocation/free responsibility, error translation, encoding, layout, callback lifetime, and runtime/thread attachment.

Zero-copy is also a trade-off:

copy cost
   vs
ownership complexity

9.3. Language Capability versus Ecosystem Capability

Practical suitability depends on:

language
 + runtime
 + standard library
 + package ecosystem
 + frameworks
 + debugger / profiler
 + deployment tooling
 + available expertise
 + maintenance horizon

Frameworks can materially change application architecture without changing language grammar.

A technically capable language can still be a risky project choice if expertise is scarce, a critical package is unmaintained, or target-platform support is weak.

Rewriting a legacy system is not automatically modernization; business rules, data migration, integration, tests, and user workflows are migration assets and liabilities. This connects language choice with Software Engineering.

9.4. Heterogeneous Hardware and multi-level Intermediate Representations

source
 -> AST
 -> IR
 -> machine code

is often expanded in heterogeneous compilers:

high-level tensor operation
       |
       v
loop / memory transforms
       |
       v
vector / SIMD operations
       |
       v
GPU / CPU target operations
       |
       v
machine code

LLVM- and MLIR-style systems make intermediate representation a first-class architectural layer.

The same algorithm may specialize for x86 SIMD, Arm SIMD, GPUs, or accelerators.

high-level intent
      +
target information
      |
      v
specialized kernel

New language/compiler experiments such as Mojo explore Python-like productivity together with static specialization, ownership, SIMD, and heterogeneous code generation. Conceptual promise and production maturity should be evaluated separately.

9.5. Language Evolution and the Compatibility Budget

Long-lived languages balance:

innovation
  vs
backward compatibility

Source compatibility, binary compatibility, and behavioral compatibility are different guarantees.

Edition or language-mode mechanisms can let new code adopt new rules without breaking all old source at once.

A healthy deprecation path is:

warning
 -> replacement
 -> migration tooling
 -> transition period
 -> removal

Language design includes the lifecycle of existing code, not just the elegance of new syntax.

9.6. Security Is Partly Language Semantics

Memory-unsafe operations can create out-of-bounds access, use-after-free, double-free, dangling references, and data races.

A memory-safe language does not guarantee a secure application, but it can remove or constrain specific low-level bug classes.

9.6.1. Safe Majority and Explicit Unsafe Region

safe majority
   |
   +--> explicit unsafe region

narrows the review surface.

9.6.2. Capability-oriented Design

Passing limited authority through objects or references can reduce ambient/global authority.

9.6.3. FFI Breaks the Safety Envelope

memory-safe language
       |
       v
FFI boundary
       |
       v
unsafe native code

Language-level guarantees do not automatically cross a native boundary.

9.7. Replace “Best Language” with a Suitability Model

language suitability
   =
problem fit
 x runtime fit
 x ecosystem fit
 x hardware fit
 x team fit
 x lifecycle fit

This is not a literal numeric score. It emphasizes that one severe mismatch can dominate several strengths.

A system can legitimately use different languages by layer:

service layer       -> Java / C#
AI orchestration    -> Python
native hot path     -> C / C++ / Rust
logical inference   -> Prolog / rule engine
web client          -> JavaScript / TypeScript

Final questions:

  1. Does the semantic model fit the problem?
  2. Which bug classes move to compile-time checks?
  3. How are memory and resource lifetimes managed?
  4. Does the concurrency model fit the workload?
  5. Does the runtime fit latency and startup constraints?
  6. Are FFI/ABI boundaries manageable?
  7. Are target OS/CPU combinations supported?
  8. Are debugging, profiling, and observability adequate?
  9. Is the package/framework ecosystem maintained?
  10. Can the team sustain the system for years?

Programming-language study is not about declaring a winner. It is about understanding the contract between abstraction and cost.

9.8. Multidimensional Classification and a Standard Language Profile

A language should be analyzed across multiple design axes rather than reduced to one label. This unit turns earlier concepts into a repeatable comparison method.

9.8.1. Classifying Programming Languages Across Multiple Dimensions

A language should not be reduced to a single label such as compiled, interpreted, object-oriented, or functional. A more useful profile spans execution, paradigms, types, evaluation, state, memory, scope, control flow, concurrency, metaprogramming, runtime architecture, target platform, safety, verification, application domain, and abstraction level.

These axes are largely orthogonal. A language can simultaneously be statically typed, functional, garbage-collected, JIT-capable, and actor-oriented.

9.8.2. A Standard Profile for Analyzing a New Language

For every language, ask about purpose, general-purpose versus DSL status, paradigms, type system, generics, memory/resource lifetime, evaluation strategy, error model, concurrency, runtime, metaprogramming, safety, FFI/ABI, real-time suitability, target platforms, ecosystem maturity, and long-term maintainability.

Important distinctions include:

language              != compiler
static typing         != explicit annotation
dynamic typing        != weak typing
type inference        != dynamic typing
compilation           != AOT only
interpretation        != no compilation
concurrency           != parallelism
async                 != multithreading
functional            != purely functional
immutable             != stateless
garbage collection    != memory safety
object-oriented       != class-based
real-time             != fast
low-level             != automatically fast
high-level            != automatically slow

The goal is not to declare one best language; it is to expose the guarantees and costs of each abstraction choice.

9.9. Java and C#: Similar Syntax, Different language-design Choices

Java and C# share C/C++-family surface syntax, class-based object orientation, interfaces, exceptions, garbage collection, generics, metadata, virtual-machine ecosystems, and JIT compilation. Those similarities can obscure substantial differences in type modeling, runtime semantics, generic implementation, callable objects, API syntax, error contracts, and native interoperability.

The purpose of this comparison is not to rank the languages. It is to use two mature languages that solve similar classes of problems as a concrete language-design laboratory.

Version context: as of September 2026, Java SE 27 is the latest Java SE release and Java 25 is the current LTS line. C# 14 is the current C# release supported by .NET 10. Older Java-versus-C# material must therefore be filtered for obsolete platform and feature claims.

9.9.1. Primitive Types, Value Types, and the Unified Type Model

Java separates primitive values such as int from reference types such as Integer.

C# integrates built-in numeric names with CLR value types: int is the C# alias for System.Int32, and user-defined struct types participate in the same value-type model.

Java records are concise data-carrier classes, but ordinary Java records and classes remain reference types rather than C#-style user-defined value types.

9.9.2. Boxing

Both ecosystems bridge value-like data and object-oriented APIs. Java boxes primitives into wrapper objects; C# boxes value types when they are converted to object or an implemented interface.

The allocation and identity effects of boxing matter in generic, reflective, and interface-heavy code.

9.9.3. Numeric and Enum Models

C# includes built-in unsigned integer types and decimal. Java relies on library classes such as BigDecimal for arbitrary-precision decimal arithmetic.

Java enums are rich reference types that can contain fields and methods. C# enums are value types backed by integral representations. This changes how behavior, serialization, reflection, and flags are modeled.

9.9.4. Nullability

Java reference types may be null; primitive values cannot. Wrapper types can be null.

C# distinguishes nullable value types (T?, represented through Nullable<T>) from nullable reference type analysis (string?, MyClass?). Nullable reference types are primarily a compile-time flow-analysis system rather than distinct CLR classes.

Java Optional<T> can model absence in selected APIs, but it is not a direct equivalent of C# nullable reference types.

9.9.5. Generics: Erasure versus Runtime Reification

Java generics are largely implemented through type erasure. The compiler performs generic checking and emits ordinary JVM classes and methods; generic signatures can remain as metadata, but List<String> and List<Integer> are not distinct runtime JVM classes.

C# generics are integrated into the CLR type system. Constructed generic type arguments remain available to the runtime and reflection system.

This difference affects runtime type inspection, generic constraints, arrays and reflection rules, and code generation for value types.

A precise statement is preferable to the oversimplification that Java generics “exist only at compile time”: generic metadata remains observable, but most parameterized type identity is not reified in the same way as CLR generics.

9.9.6. Variance

Java normally expresses generic variance at the use site with wildcards such as ? extends T and ? super T.

C# supports declaration-site out and in variance on eligible generic interfaces and delegates.

Both solve the same substitution-safety problem with different API ergonomics.

9.9.7. Delegates versus Functional Interfaces

C# delegates are dedicated nominal callable types and can reference static methods, instance methods, lambdas, and multiple invocation targets. C# events are built on delegate semantics.

Modern Java lambdas and method references target functional interfaces such as Predicate<T>, Function<T,R>, Runnable, or user-defined single-abstract-method interfaces.

The historical claim that Java has no delegate-like facility is therefore obsolete. The design is still different:

C#   -> delegate is a language-level callable type category
Java -> lambda is converted to a target functional interface

9.9.8. Events

C# has a language-level event member that constrains how subscribers can add or remove handlers while preserving publisher control over invocation.

Java has no equivalent event-member keyword. Listener interfaces, callbacks, reactive streams, and framework event buses are library/framework patterns.

9.9.9. Properties

C# properties combine field-like use syntax with method-like accessors.

Java has no general language-level property syntax. JavaBeans getter/setter conventions and record component accessors solve related problems through methods.

C# 14 further expands this area with field-backed properties and extension-member syntax.

9.9.10. Indexers

C# lets user-defined types support obj[index] through indexers. Java does not provide general user-defined [] overloading; collection APIs expose methods such as get and set.

9.9.11. Operator Overloading

C# permits user-defined overloads for selected operators. Java does not offer general user-defined operator overloading; behavior such as string + is defined by the language itself.

Operator overloading trades domain-specific readability against the risk of surprising semantics.

9.9.12. Extension Members

C# has long supported extension methods, and C# 14 adds broader extension-member syntax, including extension properties and static extension members.

Java has no direct language-level equivalent. Static utility methods, default interface methods, adapters, or generated APIs can solve related design problems.

9.9.13. Partial Types and Members

C# supports partial types and an expanding set of partial members. C# 14 includes partial constructors and events. This is useful for separating generated and handwritten portions of one logical type.

Java has no general partial-class mechanism; generators normally create separate types or rely on composition, inheritance, or annotation-processing patterns.

9.9.14. Interface Implementation

Both languages support multiple interfaces.

C# explicit interface implementation can make a member accessible only through a particular interface view. Java can resolve default-method conflicts explicitly, but it does not provide the same “hidden from ordinary class surface, visible through interface cast” member model.

9.9.15. Parameter Passing

Java passes all parameters by value. For object variables, the copied value is an object reference.

C# also uses value passing by default, but adds explicit ref, out, and in parameter modes.

Therefore, saying that Java is “pass-by-reference” is incorrect.

9.9.16. Checked Exceptions

Java applies the catch-or-specify rule to checked exception classes. C# does not require methods to declare or callers to catch exceptions through a comparable checked-exception type system.

This is a genuine language-design difference between compiler-enforced error contracts and documentation/API conventions.

9.9.17. Deterministic Cleanup

Java provides try-with-resources and AutoCloseable.

C# provides using, IDisposable, and IAsyncDisposable.

Both mechanisms are important because garbage collection does not provide deterministic release timing for files, sockets, locks, native handles, or database resources.

9.9.18. Annotations and Attributes

Java annotations and C# attributes both attach metadata to program elements. Their syntax, retention rules, compile-time processing, reflection APIs, and framework conventions differ.

9.9.19. Reflection and Dynamic Binding

Both platforms provide rich reflection.

C# also includes the dynamic type, which defers selected binding decisions until runtime while the language itself remains fundamentally statically typed.

Java has no direct dynamic keyword equivalent; reflection, method handles, invokedynamic infrastructure, and framework mechanisms address related needs.

9.9.20. LINQ and Java Streams

LINQ integrates query-style operations with C# and the .NET libraries. IEnumerable<T> supports executable pipelines, while IQueryable<T> plus expression trees can represent queries for translation by an external provider.

Java Streams provide powerful filter / map / collect pipelines, but the standard Stream API is not a direct equivalent of IQueryable plus expression-tree translation. Java ORM ecosystems use separate query languages and DSLs for provider translation.

9.9.21. Expression Trees

A C# lambda can bind either to a delegate or, in appropriate contexts, to an inspectable expression tree. That distinction is foundational for LINQ providers.

Java lambdas target functional interfaces. The standard language/runtime model does not expose a general C#-style expression-tree representation of arbitrary lambda syntax.

9.9.22. Asynchronous Programming and Lightweight Concurrency

C# async / await is a language-level control-flow model built around task-like awaitables.

Modern Java approaches the problem differently through CompletableFuture, executors, virtual threads, and, in Java 27, preview structured-concurrency APIs.

Virtual threads are not Java's spelling of async/await. They make large numbers of blocking-style tasks practical by scheduling lightweight threads. C# await expresses suspension in an asynchronous operation. They address overlapping application needs at different abstraction layers.

9.9.23. Unsafe and Native Interoperability

C# supports explicit unsafe regions, pointer operations, Span<T>-based low-level programming, and .NET native interop mechanisms.

Ordinary Java source does not expose raw pointer syntax. Native boundaries historically use JNI, while modern Java also provides the Foreign Function & Memory API.

Both ecosystems must define ownership, ABI, encoding, thread attachment, and error translation at native boundaries.

9.9.24. JVM and CLR Execution

Java typically targets JVM class files; C# targets the Common Language Infrastructure world. Both platforms can interpret, JIT-compile, optimize adaptively, and use forms of ahead-of-time/native deployment.

Modern .NET 10 supports NativeAOT alongside JIT execution. Java has multiple JVM implementations and native-image ecosystems.

Thus “Java is interpreted and C# is compiled” is not a meaningful modern distinction.

9.9.25. Platform Support

Old material that characterizes C# as Windows-only or primarily dependent on Mono is obsolete. Modern .NET is officially supported across Linux, macOS, and Windows. Java also has a long-standing multi-platform VM ecosystem.

The current engineering question is not merely whether a platform is supported, but how the runtime, native dependencies, packaging, operations, and performance model behave on that target.

9.9.26. Language Evolution and Compatibility

Both languages have expanded significantly while trying to preserve large installed code bases.

Java uses new syntax, restricted/contextual identifiers, source-version controls, and preview features.

C# also relies extensively on contextual keywords and language-version evolution.

As of 2026, simplistic historical claims such as “C# keeps adding features while Java stays minimal” are no longer useful. Compatibility must be analyzed across source, binary, behavioral, runtime, and tooling dimensions.

9.9.27. Java 27 and C# 14 as Current Examples

Java 27 is the current Java SE release in September 2026. Recent Java evolution includes records, sealed classes, pattern matching, switch expressions, compact source forms, and module-import improvements. Primitive patterns in instanceof and switch remain a preview feature in Java 27.

C# 14 on .NET 10 adds extension members, field-backed properties, null-conditional assignment, more partial members, user-defined compound assignment, and additional Span<T> / ReadOnlySpan<T> conversions.

The lesson is not feature counting. It is that language evolution reflects different trade-offs among backward compatibility, runtime capability, source expressiveness, and ecosystem cost.

9.9.28. Turn Each Difference into a Design Question

primitive vs value type
 -> how unified is the data model?

erasure vs reified generics
 -> how much generic information reaches runtime?

delegate vs functional interface
 -> how is callable behavior typed?

property vs accessor methods
 -> is API state exposed with field-like or method-like syntax?

checked exceptions vs no catch-or-declare rule
 -> how much of the error contract is compiler-enforced?

async/await vs virtual threads
 -> at which abstraction layer is concurrency simplified?

unsafe / PInvoke vs FFM / JNI
 -> how is native authority and memory lifetime expressed?

Read this way, Java-versus-C# comparison becomes a study of language design rather than a feature contest.

9.10. Extended Paradigms, Evaluation, and State Models

Imperative, object-oriented, functional, and logic programming are only the core. Dataflow, reactive, event-driven, symbolic, probabilistic, differentiable, and transactional models expose additional design dimensions.

9.10.1. Programming Models Beyond the Core Paradigms

Modern systems also use dataflow, reactive, event-driven, array/vector-oriented, stack/concatenative, probabilistic, differentiable, and aspect-oriented programming.

Dataflow follows dependencies. Reactive programming models time-varying streams and must address backpressure, cancellation, and error propagation. Event-driven systems route events through queues and handlers. Array-oriented languages make aggregate operations natural. Probabilistic languages integrate distributions and inference, while differentiable programming integrates automatic differentiation.

A paradigm is not a quality score; it is an expression model for particular forms of dependency, state, and effect.

9.10.2. Evaluation and State Models

Call-by-value evaluates arguments before passing values. Call-by-name may reevaluate an expression at each use. Call-by-need evaluates on demand and caches the result. Call-by-sharing is useful for explaining object-reference semantics in languages such as Python and Java.

Strict and non-strict semantics are related to laziness but are not exact synonyms.

Persistent data structures retain old versions while sharing unchanged structure. Software Transactional Memory expresses shared updates through transaction-like atomic blocks with conflict detection and retry.

9.11. Type-system Depth: from Inference to Effect Systems

Type-system design includes when information is checked, how type identity is established, what survives to runtime, and which behavioral properties can be expressed statically.

9.11.1. Advanced type-system Axes

Static versus dynamic typing is only the beginning. Other axes include nominal versus structural identity, inference, gradual/optional typing, runtime type retention, nullability, flow-sensitive narrowing, generic implementation, and soundness.

Generic type information can be erased, reified, or used to generate specialized code through monomorphization.

Refinement types add logical predicates to base types. Dependent types allow types to depend on values. Linear, affine, and uniqueness systems constrain resource usage and ownership. Effect systems make effects such as I/O, mutation, exceptions, networking, or asynchronous work explicit in type-like information.

9.12. Memory, Resource Lifetime, and Safety Models

Memory management cannot be reduced to manual management versus garbage collection. Acquisition, ownership, sharing, lifetime, release, and safety guarantees form a larger resource model.

9.12.1. Memory and Resource Management Families

Reference counting reclaims objects when counts reach zero; cycles need additional handling. ARC inserts reference-management operations through compiler support.

Tracing garbage collectors include mark-sweep, compacting, copying, generational, concurrent, parallel, and incremental families. Concurrent and parallel collection are different concepts.

Regions and arenas group objects with common lifetimes for bulk reclamation.

Resource management also covers files, sockets, locks, database connections, and GPU buffers; memory safety alone does not guarantee correct cleanup.

9.12.2. Formal Verification, Safety, and specification-level Behavior

Design by Contract makes preconditions, postconditions, and invariants explicit. Verification-oriented environments can integrate proof obligations and automated solvers.

Testing samples executions; proofs establish properties under a defined model.

Safety should be decomposed into type, memory, null, arithmetic, resource, concurrency, control-flow, and sandbox safety.

Undefined behavior, implementation-defined behavior, and unspecified behavior are different standardization concepts. Observed compiler behavior is not automatically a language guarantee.

9.13. Syntax, Control Flow, Metaprogramming, and Module Boundaries

Syntax affects parsing, tooling, macro design, and learnability. Control-flow representation and module boundaries also determine how safely programs can be transformed and composed.

9.13.1. Syntax, Control Flow, and Metaprogramming

Syntax families include C-like forms, S-expressions, prefix/infix/postfix notation, and significant whitespace. Homoiconicity describes a close relationship between program representation and ordinary data structures.

Continuations represent the remaining computation. Generators suspend and resume with retained state. Backtracking returns to choice points and tries alternatives.

Metaprogramming includes preprocessors, syntax-aware macros, hygienic macros, template metaprogramming, compile-time evaluation, source generation, reflection, introspection, runtime evaluation, and open-class/monkey-patching mechanisms.

These techniques have different consequences for static analysis, tooling, security, and AOT deployment.

9.13.2. Modules, Portability, and Abstraction Level

A physical source file is not necessarily a language module. Translation units, packages, crates, assemblies, and modules establish different compilation and visibility boundaries.

Portability can mean portable source, portable bytecode, portable low-level IR, or cross-compilation.

Machine code, assembly, low-level, high-level, and very-high-level languages describe relative abstraction distance. The historical 1GL–5GL scheme is useful for history but too coarse for modern technical classification.

9.14. Runtimes, Virtual Machines, Target Platforms, and Portability

A language does not have one inevitable execution path. Interpreters, bytecode VMs, JITs, AOT systems, native targets, GPUs, and portable low-level IRs all change the implementation model.

9.14.1. Virtual Machines, Target Platforms, and DSLs

Virtual machines can be stack-based or register-based; some interpreters directly execute an AST.

Managed runtimes can centralize GC, metadata, exceptions, verification, and JIT services. Native execution does not imply the absence of runtime services.

Target platforms include native CPUs, virtual machines, WebAssembly, GPUs, gölgelendirici işlem hattı (shader pipeline)s, and hardware-description environments.

General-purpose languages cover broad domains, while Domain-Specific Languages narrow the problem space. Internal DSLs live inside a host language; external DSLs define independent grammars.

Restricting expressiveness can improve safety, analyzability, and termination guarantees.

9.15. Application Domains, real-time Behavior, and Computational Power

Language suitability depends on domain constraints. Real-time deadlines, domain-specific restrictions, and computational expressiveness can make the same feature an advantage in one system and a liability in another.

9.15.1. Application Domains, real-time Behavior, and Computational Power

Languages can be grouped by systems, application, scientific, numerical, database, shell, configuration, smart-contract, theorem-proving, and specification domains.

Real-time does not mean fast; it means meeting timing constraints. Hard real-time treats missed deadlines as failures, while soft real-time usually treats them as degraded quality.

Determinism can refer to functional output, scheduling, timing, allocation, or I/O.

Turing completeness is not always an advantage. Intentionally restricted DSLs can provide stronger termination, security, resource, and static-analysis guarantees.

9.16. Performance Context and Engineering Decisions

Language performance is not one benchmark number. Compiler strategy, runtime behavior, allocation, warm-up, latency distribution, native libraries, and data structures all contribute.

9.16.1. Performance Classification Requires Context

“Language X is faster” is incomplete without a metric.

Relevant measures include startup, warm-up, average latency, p95/p99 latency, throughput, memory, allocation rate, energy, binary size, and compilation time.

Compilers can apply constant folding, dead-code elimination, inlining, devirtualization, loop transforms, vectorization, escape analysis, and profile-guided optimization while preserving semantics.

Tail-call optimization depends on the language/runtime guarantee. Monomorphization can improve specialization while increasing binary size.

Benchmark comparisons should report the algorithm, data, hardware, OS, compiler/runtime version, optimization configuration, warm-up procedure, and measured metric.

References

  • Alfred V. Aho; Monica S. Lam; Ravi Sethi; Jeffrey D. Ullman. Compilers: Principles, Techniques, and Tools. Pearson.
  • Benjamin C. Pierce. Types and Programming Languages. MIT Press, 2002.
  • Brendan Gregg. Systems Performance: Enterprise and the Cloud. Pearson Education, 2014.
  • C. A. R. Hoare. “Communicating Sequential Processes.” Communications of the ACM, 21(8), 1978.
  • Ecma International, ECMA-262 — ECMAScript 2026. https://ecma-international.org/publications-and-standards/standards/ecma-262/
  • Ecma International. ECMA-335 — Common Language Infrastructure (CLI).
  • Harold Abelson; Gerald Jay Sussman; Julie Sussman. Structure and Interpretation of Computer Programs. MIT Press.
  • ISO, ISO/IEC 14882:2024 — Programming languages — C++. https://www.iso.org/standard/83626.html
  • ISO, ISO/IEC 9899:2024 — Programming languages — C. https://www.iso.org/standard/82075.html
  • JetBrains, Kotlin 2.4 release documentation. https://kotlinlang.org/docs/releases.html
  • LLVM Project. LLVM and MLIR documentation.
  • Luca Cardelli; Peter Wegner. “On Understanding Types, Data Abstraction, and Polymorphism.” ACM Computing Surveys, 17(4), 1985.
  • Maurice Herlihy; Nir Shavit. The Art of Multiprocessor Programming. Morgan Kaufmann, 2008.
  • Microsoft. .NET 10 Runtime and Native AOT Documentation. https://learn.microsoft.com/dotnet/
  • Microsoft. C# 14 Language Reference and What's New in C# 14. https://learn.microsoft.com/dotnet/csharp/
  • OpenJDK. HotSpot, Virtual Threads, and Java runtime documentation.
  • Oracle, Java SE Downloads and Release Information. https://www.oracle.com/java/technologies/downloads/
  • Oracle. Java Language and Virtual Machine Specifications, Java SE 27 Edition. https://docs.oracle.com/javase/specs/
  • Oracle. JDK 27 Release Notes. https://www.oracle.com/java/technologies/javase/27all-relnotes.html
  • Peter Van Roy; Seif Haridi. Concepts, Techniques, and Models of Computer Programming. MIT Press.
  • Python Software Foundation. Python 3.14 Documentation; Adaptive Interpreter, Extension/Embedding, and Free-Threaded CPython. https://docs.python.org/3.14/
  • Robert Harper. Practical Foundations for Programming Languages, 2nd ed. Cambridge University Press, 2016.
  • Robert W. Sebesta, Concepts of Programming Languages, 12th Global Edition, Pearson, 2023.
  • Robin Milner. “A Theory of Type Polymorphism in Programming.” Journal of Computer and System Sciences, 17(3), 1978.
  • SWI-Prolog. Reference Manual; tabling, constraints, and multi-threading documentation.
  • Swift.org, The Swift Programming Language — Language Reference. https://www.swift.org/documentation/tspl/
  • The Go Project, The Go Programming Language Specification. https://go.dev/ref/spec
  • The Rust Project, The Rust Programming Language. https://doc.rust-lang.org/book/
  • W3C WebAssembly Working Group, WebAssembly Core Specification 3.0. https://www.w3.org/TR/wasm-core/
Contents