7 months ago I joined Lyft from Salesforce. It’s been interesting seeing the differences in the two companies’ tech stacks, tooling and culture. Now that I’ve completed several projects, I feel somewhat equipped to share my two cents on working in a Java vs. Python codebase. It’s certainly not an apples to apples comparison, as a developer’s experience is affected by many factors including infrastructure, tech debt, company culture, and the specific framework and libraries used1.

Programming language comparisons are also a bit of a touchy subject for some engineers, so I’ll try not to add fuel to the flame war. It’s understandably hard to remain objective given the time investment it takes to learn a language, the fact that most of us don’t have production-grade experience with many languages, and the effect language-specific expertise can have on job prospects.

But let’s dive into it.

THE GOOD

Python’s defining feature is its simple syntax.

Indents vs. curly braces

A Java programmer working in a decently sized codebase would use an IDE with linting rules. These rules would require code to be formatted with spaces/indents anyways for readability, making curly braces largely redundant. It’s been nice not having to chase down an errant curly brace.

Standalone functions

Python functions can exist outside of a class unlike in Java. In Java, I found myself creating classes just to “house” functions.

Simpler classes and fields

In Java, all classes need to have a constructor with getters and setters for public or protected fields. These can be generated by an IDE, but it’s just more boilerplate to read and review. In Python, all fields are public and private fields are prefixed with an underscore, enforced by the programmer and linting rules. There’s no need for getters and setters, although @property can be used for data derived from class variables.

Tuples

A tuple is a grouping of objects that can have the same or different type. This is especially useful for returning values from functions - in Java I found myself creating a class just to pass data when sometimes a tuple would do the job.

Data structures: simpler initialization

This has been a big win for me. Pretty self-explanatory, so I’ll just leave an example.

  • A dictionary in Python: dict = {}.
  • In Java, here’s an article on ~8 ways to initialize the equivalent data structure (hashmap). Sometimes flexibility ends up being a hassle because it places additional burden on the developer to remember and maintain best practices, which are also constantly changing.

Simpler dependency management

  • In Java + Maven: Tries to do too much - dependency management, configuration for test execution and deployments etc. Configuration files in xml format that support inheritance => poor readability and lots of ways to shoot yourself in the foot.

       <dependency>
         <groupId>group-a</groupId>
         <artifactId>artifact-b</artifactId>
         <version>1.0</version>
         <type>bar</type>
         <scope>runtime</scope>
       </dependency>
    
  • In Python + pip: requirements.txt file specifies package versions (which is also similar to package.json or yarn.lock for those coming from the front-end world):

      `keyring >= 4.1.1`
    

THE SURPRISING

There’s a few things that I’ve found surprising, but I’ve yet to decide if they can just be lumped into “good” or “bad”. Of course, I found these out the hard way (and will stumble upon more).

Variables are function scoped, not block scoped

Strange at first, but not that big of a deal if your classes and functions are properly modularized.

Object comparison: use ‘==’ instead of ‘is’

This is the subject of entire blog posts by other folks. I’ll spare the details for the footnotes2. It’s not quite the confusing shitstorm that is Javascript equality, but can be a bit confusing:

(“” + ‘1234’) is ‘1234’
True

“”.join(‘1234’) is ‘1234’ 
False

THE BAD

No static types

At first I thought it would be like the wild west. How can anyone read and understand a codebase with commits from over a thousand engineers without static typing? The solution is Mypy which supports type annotations, which we mainly use for functions. Pycharm linting + precommit hooks to run checks (ex: flake8 and mypy) ensure code is formatted properly. Mypy isn’t perfect though and some idiosyncrasies have been a time sink. Code navigation in an IDE is also not as powerful for Python than it is for Java. Searching for function references leads to lots of false positives, especially for functions with generic names like “act()”. Not sure if this is due to limitations from Pycharm or dynamic types.

Performance

It’s commonly accepted that Python code execution is slower than Java’s because the former is an “interpreted language” while the latter is a “compiled” one. The counterpoint is that whatever gains in performance are probably offset by slower speed of development in Java, at least for app development. Java or Go are more suitable for performance-intensive code, and C/C++ for very low latency systems. Back end code at any serious mid to large-sized tech company is almost never written in just one language as each has their own set of strengths and weaknesses, and teams tackling different problems will require different solutions. If you only have a hammer, everything starts looking like a nail.

CONCLUSION

Overall doing product development work, I would take Python over Java. I find I’m able to iterate faster without sacrificing much quality 3. Perhaps the real test is for the poor soul who has to build upon my code in a couple years time. But more importantly, I enjoy writing code in Python more. I think part of it is the intuitive syntax allows me to reserve my limited brainpower for higher level problems.

I’m sure many app developers have perfectly valid reasons to disagree with me though. Java has a huge following and is widely used for infrastructure or “big data” problems4. Also, if I were in a self-driving car, let’s just say I’d rather have the app logic be written in a statically typed language like Java rather than Python.

Programming languages in general evolve and try to adopt the best parts of other languages ie. more powerful type systems in JavaScript and Python, and support for functional programming in Java. How far they can evolve though is hindered by backwards compatibility though - billions of dollars of software still running on a particular language version. This is where a newer language, like Go, can have an advantage. In general, I think learning multiple languages and thinking about their differences is interesting and a beneficial exercise for anyone wanting to become a better software engineer. After all, if you only have a hammer, everything starts looking like a nail.


  1. Salesforce was using Java 8, latest is 12. Lyft uses Python 3.6/3.7, latest is 3.8. Business factors, differences in your level of experience, and the structure of the codebase (microservices vs. monolith vs. distributed monolith) also make fair comparisons difficult. ↩︎

  2. String equality in Java, Javascript and Python. In Java, ‘==’ is supposed to be used for primitives and .equals() for objects and each object should define its own equals method (if none is supplied, it will compare by reference just like ‘==’ does for objects). In Javascript, ‘==’ uses type coercion to compare values, whereas ‘===’ also checks for type equality. In Python, there are no primitives, only objects. ‘‘is”, which checks for object identity is only recommended to be used with language-specific singletons like True, False or None. ‘==’ should be used for everything else. Like Java, an eq method has to be specified, otherwise it will default to reference equality. Seems like it’s hard to write a truly correct eq method in Python in a concise way: https://stackoverflow.com/a/25176504/5142754 ↩︎

  3. I’ve shipped my share of bugs though, a few of which may have been prevented if types were checked at compile-time instead of runtime. ↩︎

  4. Anecdotally, an Airbnb staff infra engineer friend of mine codes primarily in Java, while a senior infra one at Twilio uses Scala, which runs on the JVM and I’ve heard it be described as Java with greater support for functional programming. ↩︎