Welcome to our collection of programming language explorations. From the ubiquitous to the almost-forgotten, every language here is still runnable on modern machines.
Each language guide progresses from Hello World through advanced topics, with Docker images provided for easy experimentation.
All source code is available in our Examples Repository on GitHub, auto-generated from this site’s tutorials.
View all 75+ languages we plan to cover →
Understanding Programming Languages
Programming languages differ in fundamental ways that affect how you think about and write code. Understanding these distinctions helps you choose the right tool for each task and appreciate how languages evolved.
Abstraction Level
Languages operate at different distances from the underlying hardware:
Low-level languages (Assembly, Machine Code) offer direct control over computer hardware and memory. They are fast and memory-efficient but difficult to read and write. Every CPU architecture requires different code.
High-level languages (Python, Java, JavaScript) abstract away hardware details using human-friendly syntax. They’re easier to learn and maintain, trading some performance for productivity and portability.
Mid-level languages (C, Rust) bridge the gap—providing hardware access when needed while offering higher-level constructs for everyday programming.
Programming Paradigm
How a language organizes and structures code:
| Feature | Imperative | Procedural | Object-Oriented (OOP) | Functional (FP) |
|---|---|---|---|---|
| Core Concept | Explicit step-by-step instructions that modify program state | Sequence of procedure calls that operate on data | Data and behavior bundled into objects | Computation as evaluation of pure functions |
| Organization | Statements and control flow | Procedures/functions and modules | Objects and classes | Functions and modules |
| State Management | Explicit state changes via assignments | State passed between procedures, often global | State encapsulated within objects | Avoided via immutable data and pure functions |
| Data Handling | Variables modified in place | Data separate from procedures, often global | Data encapsulated and hidden within objects | Immutable; functions produce new data |
| Key Principles | Sequential execution, loops, conditionals | Top-down design, modularity via procedures | Inheritance, polymorphism, encapsulation | Pure functions, immutability, first-class functions |
| Best Suited For | Scripts, simple automation, direct hardware control | System programming, scripts, straightforward tasks | Large-scale applications, GUIs, complex systems | Math computations, data transformations, parallelism |
| Examples | Assembly, early BASIC | C, Pascal, Fortran, COBOL | Java, C++, C#, Python, Ruby | Haskell, Lisp, Erlang, Elixir, Scala |
Declarative languages (SQL, HTML, CSS, Prolog) take a different approach entirely—you specify what you want, not how to achieve it. The language runtime figures out the steps.
Most modern languages are multi-paradigm, supporting several styles. Python supports procedural, object-oriented, and functional programming. Scala blends OOP with functional. Even C++ now has functional features.
Processing Method
How source code becomes executable:
Compiled languages (C, C++, Rust, Go) convert source code entirely into machine code before execution. A compiler analyzes the whole program, optimizes it, and produces a standalone executable. This results in fast execution but requires recompilation for each target platform.
Interpreted languages (Python, JavaScript, Ruby, PHP) execute code line-by-line at runtime through an interpreter. This enables rapid development and platform independence but typically runs slower than compiled code.
Hybrid approaches are common:
- Bytecode compilation (Java, C#, Python) compiles to intermediate code that runs on a virtual machine. Java compiles to JVM bytecode; C# to CLR bytecode.
- Just-In-Time (JIT) compilation dynamically compiles frequently-used code paths during execution, combining interpretation’s flexibility with near-native speeds.
Typing System
How languages handle data types:
Static vs. Dynamic typing:
| Aspect | Static Typing | Dynamic Typing |
|---|---|---|
| When checked | Compile time | Runtime |
| Declaration | Types declared explicitly (usually) | Types inferred from values |
| Error detection | Catches type errors before running | Type errors appear at runtime |
| Flexibility | Less flexible, more predictable | More flexible, faster prototyping |
| Performance | Generally faster (compiler optimizations) | Generally slower (runtime checks) |
| Examples | Java, C, C++, Go, Rust, TypeScript | Python, JavaScript, Ruby, PHP |
Strong vs. Weak typing:
Strongly typed languages (Java, Python, Rust) restrict operations between incompatible types. Adding a string to a number requires explicit conversion—the language won’t guess what you meant.
Weakly typed languages (C, PHP, JavaScript) perform implicit type conversions. JavaScript’s "5" + 3 gives "53" (string concatenation) while "5" - 3 gives 2 (numeric subtraction). This flexibility can lead to subtle bugs.
Type inference lets statically-typed languages feel more dynamic. Languages like Rust, Go, Kotlin, and TypeScript can often deduce types from context, giving you safety without verbose declarations.
Understanding these concepts helps you appreciate why certain languages excel at specific tasks—and why programmers often have strong opinions about their favorites.