# Programming Languages

> 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.

- Author: Muhammet Ali Köker
- Language: en
- Canonical: https://alikoker.com.tr/en/programming-languages
- Translation: https://alikoker.com.tr/programlama-dilleri
- Published: 2014-10-13T12:00:00+03:00
- Modified: 2026-09-16T03:43:48+03:00
- Verified: 2026-09-16T03:43:48+03:00
- Type: article

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. These notes connect classical programming-language concepts to current 2026 languages and runtimes.

## 1. Why study 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.

## 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.

## 3. Language design is a trade-off problem

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

```text
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.

## 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.

```text
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.

## 5. BNF and EBNF

Context-free syntax can be described using productions:

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

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

## 6. Lexer and parser

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

```text
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.

## 7. Parsing approaches

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.

## 8. 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.

## 9. Lexical and 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.

## 10. 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.

## 11. 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.

## 12. 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.

## 13. 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.

```text
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.

## 14. 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.

## 15. 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.

## 16. 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.

## 17. Generics and parametric polymorphism

Generics allow algorithms to be reused without losing type safety:

```text
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.

## 18. 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.

## 19. Functions, closures, and higher-order programming

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

```javascript
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.

## 20. Parameter-passing semantics

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.

## 21. Evaluation order and side effects

An expression such as:

```text
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.

## 22. Control flow and pattern matching

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

```text
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.

## 23. 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.

## 24. Resource management: manual memory, GC, RAII, ownership

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.

## 25. 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.

## 26. 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.

## 27. 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.

## 28. 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.

## 29. 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.

## 30. Memory models and happens-before

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.

## 31. 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.

## 32. 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.

## 33. 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.

## 34. 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.

## 35. Compiler, interpreter, JIT, and AOT

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

```text
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.

## 36. 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.

## 37. 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.

## 38. 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.

## 39. 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](/en/c-programming-fundamentals) and [C++ Programming](/en/object-oriented-programming-with-cpp).

## 40. 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](/en/java-programming).

## 41. 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](/en/csharp-programming).

## 42. 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.

## 43. 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.

## 44. 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.

## 45. 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.

## 46. 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.

## 47. A practical language-selection matrix

Instead of selecting a universal winner, make constraints explicit:

```text
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.

## 48. 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.

## 49. 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.

## 50. Related courses

For concrete implementations see [C Programming](/en/c-programming-fundamentals), [C++ Programming](/en/object-oriented-programming-with-cpp), [Java Programming](/en/java-programming), and [C# Programming](/en/csharp-programming). For grammar and formal languages see [Automata Theory and Formal Languages](/en/automata-theory-and-formal-languages); for runtime memory see [Operating Systems](/en/operating-systems-process-memory-file-io); and for the browser-host model see [Web Programming](/en/web-programming).

## 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.

## 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.

## References

1. Robert W. Sebesta, *Concepts of Programming Languages*, 12th Global Edition, Pearson, 2023. This source was used only to identify conceptual course scope and comparison dimensions; its prose and examples were not reproduced.
2. ISO, *ISO/IEC 9899:2024 — Programming languages — C*. https://www.iso.org/standard/82075.html
3. ISO, *ISO/IEC 14882:2024 — Programming languages — C++*. https://www.iso.org/standard/83626.html
4. Oracle, *Java SE Downloads and Release Information*. https://www.oracle.com/java/technologies/downloads/
5. Microsoft, *C# Language Reference* and *What's new in C# 14*. https://learn.microsoft.com/dotnet/csharp/
6. Python Software Foundation, *Python 3.14 documentation and releases*. https://www.python.org/downloads/
7. The Rust Project, *The Rust Programming Language*. https://doc.rust-lang.org/book/
8. The Go Project, *The Go Programming Language Specification*. https://go.dev/ref/spec
9. JetBrains, *Kotlin 2.4 release documentation*. https://kotlinlang.org/docs/releases.html
10. Swift.org, *The Swift Programming Language — Language Reference*. https://www.swift.org/documentation/tspl/
11. Ecma International, *ECMA-262 — ECMAScript 2026*. https://ecma-international.org/publications-and-standards/standards/ecma-262/
12. W3C WebAssembly Working Group, *WebAssembly Core Specification 3.0*. https://www.w3.org/TR/wasm-core/

## Cite This Work

Köker, M. A. (2014). Programming Languages. alikoker.com.tr. https://alikoker.com.tr/en/programming-languages

- BibTeX: https://alikoker.com.tr/en/programming-languages.bib
- RIS: https://alikoker.com.tr/en/programming-languages.ris
- CSL-JSON: https://alikoker.com.tr/en/programming-languages.csl.json
