Why Use a Dynamic Language over a Statically Typed One?

Erik Engheim
12 min readJul 10, 2019

Dynamic and statically typed languages have existed side by side for as long as there has been programming languages. They both exist because they both offer different advantages and disadvantages. For this whole time proponents and dynamic and static languages have been feuding. This debate is not new.

However I will argue that dynamic languages tend to be more frequently mischaracterized. There is often an implied assumption among static typing fans that the dynamic typing aficionados are ignorant of the advantages of static typing.

I have spent over 20 years writing code in statically typed languages, so I am well aware of the advantages. Yet I tend to prefer dynamic languages. To be clear I don’t exclusively decide on what language I like based on dynamic or static. My favorites languages are probably Julia, Go, Swift and LISP. So the group contains both dynamically and statically typed languages.

No programming language is a silver bullet. There is always a tradeoff. That is one reason why have so many languages. What tradeoff you are willing to make depends on the nature of the problem you are trying to solve, your own strengths and weaknesses as well as the engineering discipline you are following.

Correctness and Catching Bugs

Which is the exact point of going with the statically typed languages, we don’t want surprises in a production code at runtime.

This is not a problem uniquely solved by static typing, nor is it sufficient to avoid surprises in production code. Static typing is an aid, making your job of verifying correctness easier. However it only does part of the verification.

We should ideally be notified about such issues before going live

Dynamic typing aficionados would not want to go live with any system either without having tested it. The principles are not that different. You run tests before making your system live. If you look at the bugs reported on most code repositories containing code written in dynamic languages, relatively few bugs are type related.

Does static typing help? Of course it does. Most of my developer career has been with statically typed languages. As your code grows there is a certain comfort in knowing a lot of simple mistakes can easily be caught by the type system.

The Strength of the Type System Matters Even at Runtime

However all statically typed languages are not created equal. Some like e.g. C has a weak type system, and numerous type related bugs are not caught at compile time. This has significantly worse consequences than in dynamic languages: you get undefined behavior, which it is hard to trace down the root cause of. Dynamic languages tend to fail more cleanly giving you a clear stack backtrace of where something went wrong with a descriptive error message.

The for-loop, while-loop and if-statement in both C, C++ and Objective-C will happily accept non-boolean expressions in the conditional. This can cause hard to track bugs. Consider this contrived code example to multiply two numbers through a while loop. The developer forgot to put i < count in the while loop. There is no error message, just an endless loop hanging the program.

#include <stdio.h>void multiply(int count, int input) {
int result = 0;
int i = 1;
while (count) {
result += input;
i += 1;
}
printf("%d times %d equals %d", input, count, result);
}
int main(int argc, char **argv) {
multiply(9, 8);
}

Here is an example trying the same in Julia:

using Printffunction multiply(count, input)
result = 0
i = 1
while count
result += input
i += 1
end
@printf("%d times %d equals %d", input, count, result);
end
multiply(9, 8)

I put this in a file called boolean.jl. If I try to execute this code I get:

ERROR: LoadError: TypeError: non-boolean (Int64) used in boolean context
Stacktrace:
[1] multiply(::Int64, ::Int64) at boolean.jl:6
[2] top-level scope at none:0
[3] include at ./boot.jl:326 [inlined]
[4] include_relative(::Module, ::String) at ./loading.jl:1038
[5] include(::Module, ::String) at ./sysimg.jl:29
[6] exec_options(::Base.JLOptions) at ./client.jl:267
[7] _start() at ./client.jl:436
in boolean.jl:13

Meaning I am getting a clear error message telling me what went wrong TypeError: non-boolean (Int64) used in boolean context And it tells me which function in which file at what line number [1] multiply(::Int64, ::Int64) at boolean.jl:6 this error happened. Finally it tells me the bug was caused by call from [7] _start() at ./client.jl:436 in boolean.jl:13 . When using the iTerm console I can even command click on the path and line number to jump straight to the file and fix the bug.

Had this been an if-statement buried in a large code base it could have been hard to discover. My argument is basically:

A strong dynamic type system trumps a weaker static type system

Problems Not Caught by The Type System But Frequently Caught in Dynamic Languages

Let us not forget the wide variety of bugs, which the type system has no chance of catching. Problems often significantly less or non-existent in most dynamic languages.

I will use C/C++ as an example, because it is an excellent example of static typing not protecting you against many things dynamic languages actually protect you against. Consider the wide variety of memory bugs C++ allows you to have:

  • Null pointers you forget to check for
  • Getting static initialization and de-initialization order wrong.
  • Using dangling pointers
  • Using uninitialized objects.
  • Broken copy constructors. Releasing memory used by other objects.

All of these are things causing hard to catch undefined behavior. Most dynamic languages either don’t have any of these problems or would produce a sensible stack back trace early on. At least they will fail gracefully rather than start corrupting your data.

Take Julia as an example, since that is what I used in my article and what I normally write in. I have no need to write code to check if my function arguments are null, because unless I have specifically allowed it, the function will never get a null in one of its arguments. This is in issue in C/C++, Java and C# programming. You have to add a lot of checks to make sure you did not get a null pointer in. Your compile time type checker does not catch that.

Integer overflows is not something a type system will catch but languages such as Python handles that completely transparently.

Ability to Handle Errors Gracefully in Dynamic Languages

Another often overlooked aspect of dynamic languages is the ability to gracefully handle errors. One example are telephone network switches. If statically typed languages where so amazing and creating correct programs and avoid bugs they would dominate the switches. Yet the most famous programming language for programming robust and fault tolerant network switches is Erlang a dynamic language. This works because Erlang simulates a system with hundreds of separate processes that monitor each other. One process goes down, it can be restarted by a monitoring process. As Joe Armstrong creator of Erlang says:

Building fault tolerant software boils down to detecting errors and doing something when errors are detected.

A lot of the popular statically typed languages, in particular C++ is not very good at detecting errors and doing something about it. If you throw an exception in a C++ program e.g. you may not be able to recover from it, because C++ is quite bad at cleaning up after itself when an exception happens.

About half the worlds mobile communication networks are written in Erlang. WhatsApp is written in Erlang and process 65 billion messages per day. These are things which need to be robust and have high uptime, and they are running on a dynamic language.

Here is another cool case: NASA’s 1998 Deep Space 1 mission ran LISP.

Unfortunately, a subtle race condition in the code had escaped detection during ground testing and was already in space. When the bug manifested in the wild–100 million miles away from Earth–the team was able to diagnose and fix the running code, allowing the experiments to complete.

So this was not a type related problem. Static type checking would not have caught the problem. However because it ran a dynamic programming language with access to a REPL environment the NASA team was able to connect to it, diagnose the problem and change the code live. This would have been very hard to accomplish in a statically typed language.

Statically typed languages do generally not allow on the fly changing of random bits of running code. Typically you need a full recompile, and upload of the whole binary. Not to mention the difficulty of debugging statically typed code running live. The beauty of dynamic languages is that objects carry with them a lot of information about themselves which is lost in a statically typed language. That means you can introspect the system at runtime with great accuracy.

Roundtrip Times and Productivity

Time matters. All development is fundamentally about time. Given enough time you can write more redundant code. You can write more units tests or integration tests. Time buys you a lot of things.

Me personally wouldn’t care much if my compilation is taking couple of minutes, at the cost of providing me with a robust production grade code.

That may be true in ideal cases. I worked on a statically typed code base of a few million lines of code. Compiling the whole thing took about 1 hour. Usual changes would take a few minutes of compilation and then some minutes starting up the program, loading a project and testing your feature. This roundtrip added up quickly and consumed a lot of my development time.

It depends greatly on the type of work you are doing. Any type of work that required a number of frequent iterations such as GUI code was a nightmare to work with. You move a button a few pixels wrong. Compile, load program, load project, click your way to the offending dialog and boom you screwed up. Rinse repeat. This can dramatically reduce your development time and add to anger and frustration.

This is something software developers recognize, that is why levels in computer games and character behavior is almost exclusively written in dynamic languages. The game engine itself is often written in a statically typed language such as C++. But that works because it is pretty clear what it is supposed to do:

  • Render lots of graphics fast
  • Do collision detection between multiple objects quickly

Designing the gameplay however is a highly iterative and experimental process. Doing this in a statically typed language, where every change requires a recompile would be an immensely frustrating and slow experience.

It is why you see the frontend is typically written in dynamic languages like JavaScript. Graphical representations typically requires a lot of fast iterative development.

Data science and scientific computing in general is dominated by dynamic languages such as Julia, Python, R and Matlab. This is for much the same reason as dynamic languages dominating game development (gameplay part). You are often doing a lot of exploratory coding, experimenting with data.

In computer games you have lots of data in the forms of levels you don’t want to reload each time you need to make a change. Hence a language that lets you keep running in a known state while doing code changes is immensely valuable. The same challenge applies to data science. You have huge datasets you are working on which takes time to load into memory.

Having to reload these data sets each time you do a code change and recompile would negatively affect your productivity. With dynamic languages you can keep large data sets in memory along with all sorts of state you have created while manipulating this data. You can retain all this while adding functions or redefining existing ones.

This means using a statically typed language can end up costing you significantly more time than a few minutes of compiling. That is time saved which the dynamic language practitioner can use to write more tests.

Tests vs Compile Time Type Checking

This brings me to another important point. Dynamic language user do know about the problems of not having a type checker at compile time. That is why they are far more active in writing tests. They compensate by writing more tests.

You could argue that: with static typing you can save time by not writing as many tests. But that is only partially true, because as I’ve outlined before the development process for most statically typed languages is significantly slower.

Meta Programming and Productivity

One of my key complaints about statically typed languages is their complexity. Making a flexible and expressive statically typed language without making it complex is very difficult.

With dynamic languages you can use the same language for programming as you use for meta programming. In statically typed languages things like templates and generics is expressed with an entirely different language following a different set of rules which is largely opaque. It produces a different set of errors. You cannot step through template code. It is largely invisible.

Here is an interesting case from Julia: While working in the Julia REPL environment I can lookup the source code of any function, even functions made of code that was generated at runtime.

This flexibility means that you see users of dynamic languages such as LISP and Julia more actively using meta programming facilities such as macros than practitioners of statically typed languages. This can dramatically cut down on the amount of code you need to write.

Now number of lines of code is of course useless if you cannot read the code, however this goes both ways. Very dense code can be hard to decipher but so can very verbose code be as well. C++ template code for instance is notoriously hard to read.

When to Use Statically and Dynamically Typed Languages

My counterpoints against statically typed languages are not intended to suggest dynamically typed languages are always better, it is simply to push back against some common myths about the weakness of dynamic typing.

Static typing has its place. When working on a well defined problem domain where frequent iterations is less important and the code base is large, static typing will have many advantages.

Type of software decides whether getting a runtime error or compile time error is of significance. For scientists doing scientific calculations it is not important if they get a runtime error rather than a compile time error. They are programmers and not the same as an end user which has no ability to fix the problem. Making applications and scientific packages is very different.

However the choice of language does in my opinion to a larger degree depend on the programmers themselves and their style of programming. It depends on what sort of weaknesses you prefer to deal with and what sort of advantages you enjoy. If you hate writing tests, then using a dynamic language is a bad idea. If you don’t like working in a complex IDE then using a statically typed language is a bad idea.

Users of dynamic languages must invest more in sound engineering practices to write solid software. Users of statically typed languages must invest a lot more time in learning tools. Statically typed languages have far more complex toolchains and editors. The languages also tend to be more complex. Complexity has a cost. People deal with that in different ways.

Personal Reasons for Preferring Dynamic Languages

As someone who has been caring about user experience for decades I have a strong opinion on complexity. I greatly value simple systems. Looking through history at almost any kind of technology or product complexity has often played a major part in making something fail.

Simple systems are easier to reason about and hence construct correctly and discover problems with. My issue with statically typed languages is that almost all of them end up getting overly complex. C++ is a particularly grotesque example.

But even modern incarnations such as Scala are overly complex. Few Scala developers from what I read understand Scala library code or really understand the type system.

If it was only a question of the language we may have been fine. However these languages also tend to come with bloated IDEs such as Visual Studio, Eclipse etc which require extensive investment in time to master. In addition they often require the mastery of complex build systems. It quickly adds up to a lot of moving pieces you need to keep in your head at the same time.

I prefer languages which can be written with a powerful coding editor. Now you can invest a lot of time in these as well, but that is largely optional and they are generic tools. The skills you develop using an advance text editor carries over to almost any kind of work. I can use the same tool to write Markdown, XML, Julia, Go, shell scripts, configuration files, CSV files etc. And they are focused on one clear task: Manipulate text efficiently whatever that text represents.

Combining the editor with a terminal and dynamic language and you got a powerful combination. A lot of the tools users of static languages have in their IDE, I simply add as regular code. Syntax highlighting in the REPL is just a Julia package loaded into the REPL. So is the debugger and the package manager.

One interesting example this regard is the smalltalk coding environments. It is a dynamic language and the whole Smalltalk IDE is written in Smalltalk. While writing smalltalk code you alternative between writing code for your project and code to extend and modify the IDE to facilitate the work you are doing. There is a strong symbiosis which would have been hard to accomplish is a statically typed language.

The idea of work being divided into 3 separate stages:

  • writing code
  • compiling
  • running

Makes sense for statically typed languages but is often an artificial division for users of dynamic languages. In particular in a Smalltalk environment, the code is live almost constantly. The code is running while you are writing code. If your code isn’t working you will get quick feedback about that.

So the idea of making sure your code is correct before going “live” is artificial, since in many dynamic languages in particular in Smalltalk and LISP you are continuously live. The system you are building is often running while you are building it. The whole process is a lot more organic, and that is difficult to convey to long time developers in statically typed languages.

--

--

Erik Engheim

Geek dad, living in Oslo, Norway with passion for UX, Julia programming, science, teaching, reading and writing.