Cross-Language Design Comparisons

Java, JavaScript, Python, and C

Neil Ernst

University of Victoria

2026-10-30

Language Philosophy

Who should prevent bugs?

Java JavaScript Python C
Core approach Write the contract down; let the compiler check it Stay flexible; agree on conventions Be readable and flexible; trust people to behave Stay close to the machine; trust the programmer completely
Who catches a mistake? The compiler, tests A test, or a user A test, a type checker, or a user You, or a crash, or nobody

Four Bets: What They Cost and Buy

Java JavaScript Python C
Cost Ceremony: more to write and declare Errors surface late Errors surface late; discipline varies by team Memory and undefined-behaviour bugs
What it buys Early feedback; tool support at scale Speed of change; runs everywhere Concise, readable code Control; performance; small footprint

Design choices explain why the same design approach looks so different.

Java: Compiler Holds You to It

  • Types, visibility and interfaces are declared, and checked before the program runs.
  • Many design decisions are written into the code: private, final, @Override, implements.
  • The language prefers explicitness over brevity, which is why Java tends to be longer. See “kingdom of nouns” essay.
  • The compiler is the first reviewer; a design that does not fit simply does not build.

JavaScript: Convention Over Enforcement

  • Almost everything can change at run time: objects gain methods, prototypes shift.
  • Teams rely on conventions and tooling (linters, TypeScript, tests) instead of the language to enforce a design.
  • Even privacy was a convention (_name) until #name arrived; there is still no interface, no final, no overload.
  • Flexibility makes change and experimentation cheap but makes misuse easy.

Python: Flexible, and Built on Trust 🦆

  • Duck typing 🦆: if it walks like a duck and quacks like a duck, it is usable as a duck. An object works if it has the methods you call, whatever its class.
  • Privacy is a request: _name means “please do not touch”. A common Python saying is that we are all consenting adults here.
  • Readability is a stated value (“Readability counts”; “Explicit is better than implicit” in the Zen of Python).
  • Type hints an optional layer you can add when a codebase needs more safety.

C: Here Be Dragons 🐉

  • The language is a thin layer over the machine: you manage memory, you check return values, you track array lengths.
  • Casts, pointer arithmetic and unchecked access are all legal. Many mistakes are undefined behaviour, not an error message.
  • “Trust the programmer” is part of the spirit of C as described in the language’s rationale.
  • Safety comes from discipline and structure you build yourself: opaque types, naming conventions, ownership rules.

Hiding and Interfaces

Design question Java JavaScript Python C
Is hiding enforced? Yes, by the compiler Yes for #field, by convention otherwise By convention By file layout; casts can undo it
Is the interface written down? Yes, required No Optional By a struct you design

Memory and Failure

Design question Java JavaScript Python C
Who is trusted with memory and lifetime? The runtime The runtime The runtime You!
Typical failure Does not compile TypeError at run time AttributeError at run time Crash, or silent corruption

M7 — Inheritance and Reuse

Inheritance Across Languages: Declaring and Calling

Java JavaScript Python C
Declare a subtype class B extends A class B extends A class B(A) None; embed the base as the first member in a struct
Call the parent super.method() super.method() super().method() Call the base function by name

Inheritance Across Languages: Overriding

Java JavaScript Python C
Mark an override @Override (checked) Nothing Nothing Not applicable
Prevent overriding final Nothing built in typing.final (checker only) Not applicable

Inheritance Across Languages: Parents and Privacy

Java JavaScript Python C
Multiple parents One class, many interfaces One class (mixins by hand) Yes, with a computed order By embedding several structs
Private to the parent private #field _field by convention Not visible if the struct is opaque

Override and super: Java

class Employee {
    private final int base;

    Employee(int base) { this.base = base; }

    int calculatePay() { return base; }
}

class Manager extends Employee {
    private final int bonus;

    Manager(int base, int bonus) {
        super(base);
        this.bonus = bonus;
    }

    @Override
    int calculatePay() { return super.calculatePay() + bonus; }
}

Override and super: Python

class Manager(Employee):
    def __init__(self, base, bonus):
        super().__init__(base)
        self._bonus = bonus

    def calculate_pay(self):
        return super().calculate_pay() + self._bonus

@Override makes Java’s compiler reject a method that overrides nothing (a typo). Python accepts the typo and creates a new method.

Override and super: JavaScript

class Manager extends Employee {
  #bonus;

  constructor(base, bonus) {
    super(base);
    this.#bonus = bonus;
  }

  calculatePay() { return super.calculatePay() + this.#bonus; }
}

JavaScript, like Python, accepts a misspelled override as a new method.

Override and super: C

struct Employee {
    int base;
    int (*pay)(const Employee *self);
};

typedef struct {
    Employee employee;       /* "inherits" by embedding the base first */
    int bonus;
} Manager;

static int manager_pay(const Employee *e) {
    const Manager *m = (const Manager *)e;
    return employee_pay(e) + m->bonus;     /* the explicit super call */
}

Dispatch in Java

Employee employee = new Manager(100, 20);
employee.calculatePay();       // 120: the run-time type chooses the override

The declared type is Employee. The object is a Manager. The Manager method runs.

Dispatch in C

Employee *e = &m.employee;     /* a Manager viewed as an Employee */
e->pay(e);                     /* 120: through the pointer, Manager's function */
employee_pay(e);               /* 100: a direct call picks the base function */
  • Java, JavaScript, and Python dispatch on the run-time object. C dispatches only if you store a function pointer.
  • In C, forgetting the pointer gives you the base behaviour, with no error.

Compile-Time Type vs. Run-Time Type: Java

Java chooses overloads from the declared type, but overrides from the actual object.

static String describe(Object o) { return "object"; }
static String describe(String s) { return "string"; }

Object o = "hi";
describe(o);        // "object": overload chosen from the static type
describe("hi");     // "string"

Compile-Time Type vs. Run-Time Type: Python

def describe(o): return "object"
def describe(s): return "string"       # replaces the first definition

describe("hi")                         # "string": there is no overloading

JavaScript behaves like Python; C has no overloading either. Only Java makes the static type matter here.

A Subtype Must Honour the Base Contract

Every language lets you build a Stack from a list, and every one lets a client bypass the stack discipline.

Substitutability: a Stack that is a List must accept everything a List accepts. If it cannot, hold the list as a field instead.