Type Systems
A type system is the set of rules a language uses to classify values (numbers, text, objects) and constrain what operations are valid on them. Type systems are one of the biggest sources of confusion for beginners because several independent axes — static/dynamic, strong/weak, explicit/implicit — get casually lumped together.
A data type describes what kind of value a variable holds (a whole number, decimal number, text, true/false, etc.) and what operations make sense on it. Static typing checks these types before the program runs (at compile time); dynamic typing checks them while the program runs.
Types prevent nonsensical operations — like adding a number to a piece of text without a clear rule for what that means — and they let the language (or the compiler) catch a whole category of mistakes early, or optimize the program better because it knows exactly what kind of data it's working with.
Statically typed languages (C, Java, Rust, TypeScript) are common where reliability and large-team maintainability matter, and where the compiler catching mistakes early is valuable. Dynamically typed languages (Python, JavaScript, Ruby) are common where flexibility and fast iteration matter more, especially for smaller scripts and prototypes.
A statically typed language is like a shipping company that inspects every package's declared contents before the truck ever leaves the warehouse — mistakes get caught immediately, but every package must be labeled up front. A dynamically typed language is like a courier who opens each package only when it arrives at its destination — more flexible about what you can send, but a mislabeled package isn't discovered until delivery.
Java fixes the type of the variable x for its entire lifetime and checks it before running; Python lets the same variable name refer to values of different types at different times, checked only when each line actually executes.
Static vs. dynamic typing is about WHEN types are checked (compile time vs. runtime). Strong vs. weak typing is a separate axis about HOW STRICTLY the language enforces type rules and allows implicit conversions between types.
A 'weakly typed' language (classic JavaScript, C to a degree) will often silently convert between types in an operation (e.g. "5" + 1 producing "51" in JavaScript); a 'strongly typed' language (Python, Java) refuses such an operation or requires an explicit conversion.
Explicit typing means the programmer writes the type out (int x = 5; in C). Implicit typing (or type inference) means the compiler figures the type out from context, without the programmer writing it (auto x = 5; in C++, or var/let x = 5 in TypeScript with inference, or simply x = 5 in Python) — implicit typing can still be static (the type is fixed, just inferred) or dynamic.
Type safety refers to how well a language prevents type errors from causing undefined or unsafe behavior. A memory-unsafe operation caused by a type mismatch (like reinterpreting raw bytes as the wrong type in C) is a type-safety violation, distinct from memory safety, though the two overlap heavily in low-level languages.
Some languages combine axes in ways that surprise people: TypeScript is statically typed but its checks are erased before runtime (JavaScript underneath is dynamically typed); Python is dynamically but strongly typed (it won't silently coerce a string and an int in +, but it also doesn't check types until the line runs).
Treating 'static vs. dynamic' and 'strong vs. weak' as the same axis — they are independent; a language can be static+weak (C, in some respects), static+strong (Java, Rust), dynamic+weak (classic JavaScript), or dynamic+strong (Python).
Assuming dynamically typed always means 'no type errors are caught' — Python still raises a TypeError at runtime for mismatched operations; it just discovers the error later than a static type checker would.
Confusing 'type inference' with 'dynamic typing' — TypeScript, C++'s auto, and Rust's let all infer types, but those types are still fixed and checked at compile time, making the language statically typed despite the programmer not writing the type explicitly.
Adding TypeScript to a large JavaScript codebase specifically to gain static type checking without giving up JavaScript's ecosystem.
Using Python's optional type hints (still dynamically enforced at runtime, but statically checkable with tools like mypy) to get some of static typing's benefits without abandoning dynamic typing.
Choosing Rust for a systems project partly because its strong static type system, combined with its ownership model, eliminates entire classes of memory and concurrency bugs at compile time.
Type erasure vs. reified types Some statically typed languages check types at compile time and then discard (erase) that information before runtime — Java generics and TypeScript's types both work this way, meaning the information isn't available for runtime reflection in its original form. Others keep type information available at runtime (reified generics, as in C# and some Rust monomorphization scenarios) which enables certain runtime behaviors (like runtime type checks against a generic parameter) that erased-type languages cannot support directly.
Type inference algorithms Modern type inference (Hindley-Milner in ML-family languages like Haskell and OCaml, and more ad-hoc local inference in C++/TypeScript/Rust) lets a compiler deduce a static type without it being written out, by propagating constraints from how a value is used throughout the program. This is a genuinely different mechanism from dynamic typing's runtime type tagging, even though both can look similar in source code (no explicit type annotation).
Structural vs. nominal typing Nominally typed languages (Java, C++) consider two types compatible only if they're explicitly declared to be related (same class, or an explicit inheritance/interface relationship). Structurally typed languages (TypeScript, Go's interfaces) consider two types compatible if they simply have the same shape (the same fields/methods), regardless of declared relationship — a distinct design choice with real consequences for how libraries and APIs are designed.
Static typing lets a compiler know exact memory layouts and operation types ahead of time, enabling optimizations (direct machine instructions, no runtime type checks) that a fully dynamic language typically cannot perform without additional analysis (which is exactly what a JIT compiler's type specialization and inline caching attempt to approximate at runtime).
What's the difference between static and dynamic typing?
Static typing checks and fixes a variable's type before the program runs (at compile time); dynamic typing checks a value's type while the program is running, and the same variable name can refer to different types at different times.
What's the difference between strong and weak typing?
Strong typing means the language enforces type rules strictly and generally disallows implicit, potentially unsafe conversions between unrelated types. Weak typing allows more implicit coercion between types, sometimes producing surprising results (e.g. "5" + 1 becoming "51" in JavaScript).
Is Python statically or dynamically typed? Strongly or weakly?
Python is dynamically typed (types are checked at runtime, and variables can be reassigned to different types) and strongly typed (it raises a TypeError rather than silently coercing incompatible types, e.g. "5" + 1 fails in Python).
Can a language be both statically typed and use type inference?
Yes — type inference only changes whether the programmer writes the type explicitly; it doesn't change when the type is checked. Rust, TypeScript, and modern C++ all infer types while still checking and fixing them at compile time, so they remain statically typed.
Output-based: what does "5" + 3 evaluate to in JavaScript, and why?
"8" — no, this is a trick: it actually evaluates to the string "53". JavaScript's + operator, when one operand is a string, converts the other operand to a string and concatenates, due to JavaScript's weak typing rules for that operator.
Java (static):
int x = 5;
x = "hello"; // compile-time error: incompatible types
Python (dynamic):
x = 5
x = "hello" // perfectly legal — x now just refers to a string