How to understand new programming languages
Published November 7, 2025
In my quest to learn basic computer science concepts, I have dipped my toes in and learned the basics of many different programming languages. I find that a good framework to figure out how each of them work is to understand what the language makes easy and what it makes difficult. Every language is designed in a certain way, and they all have advantages and disadvantages. Here, I want to cover Python, Rust, Haskell, and C. They are all general-purpose languages all capable of essentially the same things, but do things differently and encourage vastly different styles.
| Language | Design Philosophy | Advantages | Disadvantages |
|---|---|---|---|
| Python | Simplicity | Easy/fast to read/write code, easy to learn | Slow performance |
| Rust | Correctness | Fast performance that rarely crashes | Strict compiler |
| Haskell | Mathematical purity | Easy to reason about code to ensure it does what you want | Strict compiler, high learning curve |
| C | Maximum low level control | Best performance with maximum control | High learning curve, easy to introduce bugs |
Writing working projects quickly
This is obviously a desirable quality. While familiarity with the language is obviously the biggest factor that determines how quickly you can write programs, certain languages just take care of things for you that you have to do manually in other languages.
Python
This is one of Python’s most obvious strengths, and one of its core design features. Once you have an idea, you can implement it very easily. There is no type system, so variables are automatically converted for you whenever possible.
Rust
Rust’s compiler forces you to check and handle almost every possible condition that your program can face. If you take user input, you better have code that makes sure that input is the type that you want. If you want to add two numbers, but one is a 16-bit integer and one is a 32-bit floating point number, you better convert one and specify your return type or your program won’t compile. The upside to this strict compiler is that if it compiles, it probably works.
Haskell
Similar to Rust, Haskell has a strict compiler when it comes to types. It will not add integers and floating point numbers unless you specifically convert them. Haskell also is strict about its use of side effects, so certain tasks that are easy in other languages, like taking user input or printing to the screen, are more tedious. Also, the lack of loops often forces you to find a recursive solution to problems that lend themselves more naturally to loops.
C
The C compiler is more generous than Rust’s, but just because it compiles certainly doesn’t mean that it works. C is used when you require the most control over the computer, and therefore needs you to manually tell the computer what to do, down to manipulating bits and managing memory addresses.
Not crashing during runtime
This is another obviously desirable property. If your program frequently crashes, who cares how fast you can write it.
Python
Since Python has no compiler or type checking, passing an incompatible argument to a function causes programs to crash. You have to think of every possible way a program can fail, and handle that case. In contrast to a language like Rust, there is nothing to help you find all the possible errors. When writing simple tools for only yourself, this issue is mostly fine because you can just be sure to not input any bad data, but when ensuring arbitrary input doesn’t cause your program to fail, this is undesirable.
Rust
The upside to the strictness of Rust’s compiler, and the reason people use it, is that once your program compiles, it probably will work no problem. The compiler won’t compile if it detects even the slightest possibility of an issue that isn’t properly handled.
Haskell
Haskell has a similar feeling to Rust, where if it compiles, it will probably work just fine.
C
It is so easy to write a C program that crashes, it’s not even funny. Since you have pretty much full control and a compiler with essentially no guardrails, there are so many errors that can come up causing your program to crash or behave unexpectedly.
Returning functions from functions
This is a pattern I find myself using a lot, for better or for worse. I don’t know how common this design pattern is for other people, but the solutions that I come up with often involve functions that create another function. This is a concept called first class functions, where functions are able to be passed as arguments to a function, and returned as the output. A function that incorporates this is known as a higher order function.
Python
While very possible, it is often annoying, and the lack of a type system makes it hard to track down what type of arguments a function takes. I don’t think this is a “core” feature to Python, but I do it very often nonetheless.
Rust
Rust does support higher order functions. I’m not that familiar with how they work.
Haskell
Since everything in Haskell is a function, it is nearly impossible not to do this. Through a concept called currying, returning a function from a function is as easy as not supplying all of the necessary parameters. This is one feature that initially drew me to Haskell, since I had already basically reinvented this concept in some of my Python code. Seeing it encouraged as a full named feature in a language for the first time was enlightening.
C
C doesn’t allow you to define functions inside of functions, so this ability is limited. Functions are also not first class, so they cannot be passed as arguments to other functions. However, you can pass pointers to functions as arguments, and return pointers to functions as output. However, the functions must already exist at compile time.
Writing fast code/algorithms
I’m very interested in writing very efficient, elegant algorithms. This is one big reason why I started looking into languages other than Python. I wanted to be able to write efficient, custom algorithms, and be able to understand how people implement them. One goal I have is to write a Python package in Rust or C.
Python
This is where Python falls short. While super easy to learn and prototype ideas in, it is notoriously slow. It is an interpreted language, so it is very slow compared to compiled languages. Languages like C, C++, or Rust are 80-200x faster than it. There are many projects, such as Cython or Apache Arrow to try to make it faster. Often in data science, Python is used sort of as a glue that combines a bunch of libraries that are written in much faster languages.
Rust
This is one of the design philosophies of Rust, and the compiler optimizes code so much that it feels like magic. Before using Rust, I had used R and Python, and often wondered why I often heard people complaining about how slow Python was. It seemed to run pretty instantaneously for my purposes. After using Rust, I finally understood. I implemented the same logic for an algorithm for a project Euler problem in both Python and Rust, and Rust was literally 80x faster.
Haskell
Haskell is compiled as well, and often I find it to be fast. As a pretty high-level language that doesn’t encourage you to get down to hardware level instructions, the Glasgow Haskell Compiler (GHC) does an incredible job optimizing code. Since the type system is so comprehensive and strict, and side effects are explicitly limited to a certain section, it is free to make huge optimizations. The limitation is that you rely on the compiler to optimize the code for you. It is difficult to know what the computer is doing and find your own optimizations.
C
C is the ultimate solution to writing the fastest, most elegant algorithms. You can get so low level and tell the computer exactly how you want something done. Look no further than the fast inverse square root algorithm described here. I find it so brilliant how they exploit the way computers store numbers to compute a value extremely quickly.
Readability
Being able to read code and understand what it does is obviously nice. A program being easily readable and understood is not the sole job of the language designers, however. No matter how elegant the language’s syntax is, programmers will find ways to write the most confusing mess possible.
Python
Once you are familiar with Python, reading code is basically English compared to other languages. You can literally write
if some_variable is 67:
do_this()
else:
do_that() Rust
Rust is only about 10 years old, and has more modern syntax than some languages that have been around for 50 years. The designers saw what features worked, what didn’t work, what people wanted, and wrote a new language that would be easier to use. I don’t find the syntax to be a problem, the difficulties lie in satisfying the rules of the compiler.
Haskell
Haskell is notoriously difficult to learn, and Haskell programmers tend to write very terse code with short variable names. I don’t find my own code difficult to understand, but some examples I see online feel like magic. They find so many tricks that exploit the lazy evaluation and recursion that make it so difficult to understand sometimes. The way complicated category theory and functional programming concepts like functor, monoid, monad, comonad, semigroup, foldable, polymorphism, or traversable (just to name a few) get thrown around as doesn’t help either.
C
I’m very new to C, but I think it can be pretty readable if the programmer chooses to make it. There are many tricks that people use that can make it much more confusing to tell what is going on.
Knowing precisely what the computer is doing
This is essentially thrown in so C has a category to really shine. From my limited viewpoint, this is maybe the primary reason to use C over another language that has some nicer features.
Python
In general, you have no idea how the computer runs your code. You are very abstracted away from how it works.
Rust
While the code is still a little more abstracted than C, you do know how the computer is working, and you can get to the hardware level if you like.
Haskell
Like Python, you have no idea how the computer runs the code, and rely on the compiler for a lot.
C
It’s literally impossible to write a C program that works without knowing what the computer is doing. The program is a set of explicit instructions telling it how to do something.
Final thoughts
Personally, I really like the way Haskell encourages you to write your programs. When I first learned R, and then Python, I essentially reinvented the functional style of programming. As someone with more of a math background, that’s just what made the most sense to me naturally. Why wouldn’t you want your functions to return the same output if it’s given the same input? I just always structured my functions this way, even before understanding the concept of a pure function.
What can be annoying sometimes in Haskell is the insistence on no side effects. Some functions are much easier understood with a loop than with recursion, like performing a set number of iterations of an algorithm, or checking a condition up to a certain value. Also, the fact that printing and getting input has to be dealt with as a side effect is a nuisance, but I understand why.
I find that understanding a language by seeing what it makes easy is a great way to learn the language and write it the way it wants to be written. If something basic in one language feels like a hack in another, there is almost always a better way to do it.