Photo by Glenn Carstens-Peters on Unsplash

Typing: static or dynamic, strong or weak, safe or unsafe

Nick Doornekamp
5 min readJun 6, 2022

--

When it comes to typing (i.e. the type system of a programming language, not on a keyboard), there’s a lot of terminology around. Amongst others, typing is often classified as:

  • Static or dynamic
  • Strong or weak
  • Safe or unsafe
  • Nominally, structurally or duck-typed

There seems to be a lot of ambiguity around about what these terms mean, which arguably makes them more confusing than useful. Below I’ve written down the result of the research (read: Googling) I did to get (closer) to the bottom of it.

Static vs. Dynamic

First of all: a programming language is often classfied as either statically or dynamically typed. This is about when type information is acquired: at runtime or before.

Static typing

In statically typed languages, types are checked before runtime. I.e. in case your code contains a type error, you will be presented with that error before (any of the) the code runs. A type error is an error or undefined behavior that arises when a program attempts to perform an operation on a value on which that operation is not defined.

Therefore, the type of a given variable has to be know known without executing the programe. To achieve that, static typing often goes hand in hand with explicit declarations of types by the programmer. This is called manifest typing or explicit typing. In contrast, some (statically typed) languages such as Haskell and Scala use type inference or implict typing, where the types are deduced from context before runtime.

Dynamic typing

In dynamically typed languages, types are checked at runtime. I.e. if your code contains a type error, you will only be presented with an error once the statement that has the error is executed. The statements before that are executed as usual. Also, if the code has a statement that results in a type error but the statement is only executed in special cases, the code will run perfectly fine until the special case occurs.

Compiled vs. Interpreted

Interpretation and dynamic typing almost always go hand-in-hand, as do compilation and static typing. There are some exceptions though: there are statically typed languages that are interpreted and dynamically typed languages that are compiled.

Strong vs. Weak

Compared to ‘static vs. dynamic’, the distinction between strong and weak typing is less clearly defined. Unlike static/dynamic, it’s not a binary classification, but a spectrum. In general, it is used to indicate how ‘relaxed’ a language is when it comes to types: languages that are relaxed about types are said to have weak typing; languages that are strict are said to have strong typing.

A good illustration of what is meant by this is the extent to which a languages does implicit type conversion: Are you required to do type conversions explicitly, or will the language do them for you? An example of a languate that is very relaxed about type conversions is JavaScript:

Python is stricter about this than JavaScript, so we say it is more strongly typed. For example, statements like the above will result in errors:

To make those kinds of statements work, you have to convert the type explicitly:

However, an example of weak typing behavior in Python are ‘truthy’ and ‘falsy’ values:

It’s often said that typing in Javascript is weaker than in Python, which is in turn weaker than, for example, Java or C#.

It’s interesting to note that the position of a language on the strong/weak spectrum is independent of whether it is a statically or dynamically typed: there are statically typed languages that are on the weak side of the spectrum and dynamically typed languages that are on the strong side.

Safe vs. unsafe

Unlike strong typing, type safety does have a clear definition: type-safe operations do not result in a type error. Here the definition of a type error is quite strict, none of the examples in this blog are type errors. The result of 4 + ‘2’ in JavaScript may be weird, but it is well-defined, so not a type error. Therfore the addition of an integer and a string is type-safe in Javascript. Similarly, the TypeError raised by Python is well-defined, so no type error and therefore that operation is type-safe in Python.

In a type-unsafe language, some operations applied to some types of values have undefined results, meaning the results can be upredictable and depend on the machine executing the program. Proving type-unsafety is relatively easy, as it can be done by counter-example. For example, for C/C++ there are programs that demonstrate type-unsafety. By the way: these are well-known behaviors which are deliberately undefined, to allow for more control and therefore the potential for better performance.

It’s hard to actually conclude that a language is type-safe, as the only way to conclude that a language is type-safe is through proving that every single program you can write is type-safe, which is typically not feasible. Therefore you can find statements like “Python is believed to be type-safe” when looking into this topic. In practice that is typically more than enough to work with.

Nominal, structural and duck typing

Duck typing

In languages that use duck typing, you may always (try to) invoke a method, or read a property, no matter the name or structure of the object: If the method/property is defined on that object, the program will run without errors.

It is not required to define what the type of the argument of make_it_quack() is. It’s possible to pass anything to that function, and the program will run until the error arises.

Note that the code ran right until the (nonexistent) quack() method was invoked: the print statement right before that ran without any complaints.

To explain duck-typing, it is often said that “If it walks like a duck and it quacks like a duck, then it must be a duck”, but a variant that seems to explain it better is:

I don’t care if it’s a duck — I’ll just ask it to quack and see what happens.

Nominal typing

In nominally typed languages, the name of the a type of an object determines what it can be used for. Even if two objects are structurally the same (i.e. they have the same fields/attributes/methods), they will not be treated the same if they have a different type. You aren’t allowed to pass an object of type B to a method that takes an object of type A as an argument, even if the object of type B has the same structure as an object of type A.

So even though Duck and Mallard both have quack() defined, Mallard is not accepted by makeItQuack, only objects of the (super)type Duck will be accepted. In other words:

If it’s not a duck, you can’t ask it to quack.

Structural typing

Structural typing can be said to be in between duck typing and nominal typing. Instead of demanding an object to be of a specific type like in nominal typing, only a certain structure is demanded.

Here, instead of explicitly specifying which class(es) the input arguments of Greeter are allowed to be, we only specify that it should have a name that is a string.

I don’t care if it’s a duck — If it can quack, I can ask it to quack.

--

--

Nick Doornekamp

Software Engineer at Alliander | Cycling & speedskating enthusiast