What trying out C# taught me about programming in Python

Nick Doornekamp
5 min readOct 9, 2022

Over the past years Python has been my preferred tool in most situations, but I recently decided to give C# a try. There are some clear differences, some of which gave me new insights about programming in Python as well. Three of those differences and my main takeaway are described in this blog.

Differences between C# and Python

Compiled vs. interpreted

Code written in high-level programming languages such as Python and C# all require conversion to lower-level instructions that a computer can execute. C# and Python represent the two main types of conversion: C# is compiled, Python is interpreted.

With C#, before you can run it, your code is compiled to a format that can be processed by the computer. If you write C# code that is invalid, this compilation will fail.

Python does not have such a compilation step: the Python code is translated to lower-level instructions ‘on the fly’. The interpreter reads the code instruction by instruction and directly executes it. Only when the instruction is executed (at runtime), errors in the instruction are caught.

Interpreted languages have the benefit that it saves the compilation step in your development process (although there are plenty of tools that make the compilation step relatively painless), at the price of the possibility of running into errors that would otherwise have been caught by a compiler only at runtime.

Development speed and performance

The consensus seems to be that C# code typically runs faster than the equivalent Python code, at least partly due to the previous point. On the flip side, the Python code will typically be simpler, more compact and quicker to write, at least initially.

All else being equal, a fast program is better than a slow one of course, but personally I think that this is factor only carries significant weight in situations where performance is of critical importance. Usually the type and complexity of the application, the experience and preference of the engineer(s), development speed (“CPU cycles are cheap, programmer cycles are expensive” — within the bounds of reason) and the availability of relevant (open source) modules carry more weight in the decision.

Furthermore, it’s not hard to imagine programmers with a lot of Python experience but little experience with C# will write Python with better performance than the C# equivalent written by them, while spending less time doing so.

Type checking

Python is typed dynamically, C# is typed statically (read about my understanding of these terms here). As a developer writing C# you’re required to specify the type of your variables, and unless you’re very explicit about it, the type of a variable is fixed. That is not the case in Python: declaring types is optional and there are no restrictions on changes to the type of a variable as the code is executed.

It’s this last difference is the one that gave me the most useful insights, even if I don’t end up using C# any time soon.

My main takeawy: static type checking is more useful than I thought

Based on some quick glimpses into statically typed languages, I thought typing mostly meant extra work for the programmer. I felt it was in the way of getting my code to do something useful quickly. What I did not fully realize, was how often I’d spend small nuggets of time debugging situations like the following (stylized) example:

Perhaps you’ve already spotted that there’s an error hidden in this simple code: If additional_adresses is not empty, the type of addresses changes to set and the function will throw an error, because sets can’t be serialized to JSON. Kind of silly, easy to fix, but also easy to overlook. Best case scenario when you make a mistake like this is that the code fails when it’s tested, worst case it goes unnoticed and only breaks when the condition that triggers it occurs in production.

In a programming language where addresses has a static type, this kind of bug is impossible to introduce, as you’d be required to declare the type of that variable and be unable to change it implicitly.

So while it is extra work, static type checking can save you time in the future. You may think of it as insurance. You have to pay the premium up-front, but in return you may be saved from negative outcomes in the future. Furthermore, declaring types can also help documenting your code, and help your development tools understand your code.

Best of both worlds(?)

None of the above are original thoughts (see e.g. these slides by Guido van Rossum from January 2000) and nowadays Python’s dynamic typing is not necessarily a reason to abandon your Python code in favor of a statically typed language.

Python comes with optional type annotations:

Straight out of the box these annotations don’t do anything beside documentation, but an IDE like PyCharm or VSCode with Pylance will make use of these annotations to draw your attention to type-bugs:

Many IDE’s can alert you to (potential) type bugs

There are also modules like mypy, which will throw an error if the code is not in line with the type annotations in your Python code.

Mypy is a static type checker for Python

To get the most out of mypy, you could include it in your CI pipeline to reduce the chances of it being skipped accidentally.

Typing or testing (or nothing)?

A final (open-ended) thought: In a lot of situations it’s a good idea to write tests and some people argue that properly tested codebase doesn’t need typing to prevent type bugs. Or vice versa: one could argue that code in a statically typed language needs less tests. It’s also valid to do both, because of the other benefits to typing such as documentation.

--

--

Nick Doornekamp

Software Engineer at Alliander | Cycling & speedskating enthusiast