Python
-
I wrote about why you should stop using pip. Poetry or uv. Pick one. pip has no lockfile, no dependency resolution worth trusting, and no isolation by default.
Have you moved to uv yet? Still happy with poetry? How’s it going?
/ Programming / Tools / Python
-
I wrote about when to reach for Python over Bash. Most veterans put the cutoff at 50 lines. If you’re writing nested if statements in Bash, you’ve already gone too far.
Where does Go fit in though? Overkill for scripts?
/ Programming / Python / Bash
-
When to Use Python Over Bash
When to use python over bash is really a question of when to use bash. Python is a general-purpose language that can handle just about anything you throw at it. Bash, on the other hand, has a very specific sweet spot. Once you understand that sweet spot, the decision makes itself.
What Bash Actually Is
Bash is an interactive command interpreter and scripting language, created in 1989 for the GNU project as a free software alternative to the Bourne shell. It pulled in advanced features from the Korn shell and C shell, and it’s been commonly used by Unix and Linux systems ever since.
What makes Bash unique is its approach to data flow programming. Files, directories, and system processes are treated as first-class objects. Bash is designed to take advantage of utilities that almost always exist on Unix-based systems. So think of tools like
awk,sed,grep,cat, andcurl. Another important thing to know when writing effective Bash scripts, you also need to understand the pipeline operator and how I/O redirection works.A good Bash script will look something like this:
#!/bin/bash set -euo pipefail LOG_DIR="/var/log/myapp" DAYS_OLD=30 find "$LOG_DIR" -name "*.log" -mtime +"$DAYS_OLD" -print0 | xargs -0 gzip -9 echo "Compressed logs older than $DAYS_OLD days"Simple, portable, does one thing well. That’s Bash at its best.
Where Bash Falls Short
Bash isn’t typed. There’s no real object orientation. Error handling is basically
set -eand hoping for the best. There’s notry/catch, no structured exception handling. When things go wrong in a Bash script, they tend to go wrong quietly or spectacularly, with not much in between.Python, by contrast, is optionally strongly typed and object-oriented. If you want to manipulate a file or a system process in Python, you wrap that system entity inside a Python object. That adds some overhead, sure, but in exchange you get something that’s more predictable, more secure, and scales well from simple scripts to complex logic.
Here’s that same log compression task in Python:
from pathlib import Path import gzip import shutil from datetime import datetime, timedelta log_dir = Path("/var/log/myapp") cutoff = datetime.now() - timedelta(days=30) for log_file in log_dir.glob("*.log"): if datetime.fromtimestamp(log_file.stat().st_mtime) < cutoff: with open(log_file, "rb") as f_in: with gzip.open(f"{log_file}.gz", "wb") as f_out: shutil.copyfileobj(f_in, f_out) log_file.unlink()More verbose? Absolutely. But also more explicit about what’s happening, easier to extend, and much easier to add error handling to.
The Performance Question
In some cases, performance genuinely matters. Think high-frequency trading platforms, edge devices, or massive clusters. Bash scripts excel here because there’s almost zero startup overhead. Compare that to Python, which needs to load up the interpreter before it can start executing code. You’re going from microseconds to milliseconds, and sometimes that matters.
But startup time is just one factor. When you compare the actual work being done, Python can pull ahead. String manipulation on structured data? Python wins. Parsing JSON, YAML, or any structured format? Python’s core libraries are written in C and optimized for exactly this kind of work. If you find yourself reaching for
jqoryqin a Bash script, that’s a strong signal you should be using Python instead.The Guidelines People Throw Around
You’ll see a common guideline online: if your script exceeds 100 lines of Bash, rewrite it in Python. But a lot of veterans in the industry feel like that cutoff is way too generous. Experienced engineers often put it at 50 lines, or even 25.
Another solid indicator: nested
ifstatements. Some people say “deeply nested” if statements, but let’s be honest, more than one level of nesting in Bash is already getting painful. Python handles complex branching logic far more gracefully, and you’ll thank yourself when you come back to maintain it six months later.Unit Testing Tells the Story
You can do unit testing with Bash. BATS (Bash Automated Testing System) exists, and ShellCheck is useful as a lightweight linter for catching bad practices. But despite these tools, Python’s testing ecosystem is on another level entirely. It’s fully mature with multiple frameworks, excellent mocking capabilities, and the ability to simulate network calls, external APIs, or system binaries. Complex mocking that would be difficult or impossible in Bash is straightforward in Python.
If your script needs solid testing or if it’s doing anything important, that’s a strong vote for Python.
Bash’s Biggest Win: Portability
So what does Bash actually win at? Portability. When you think about all the dependencies Python needs to run, Bash is the clear winner. You’re distributing a single
.shfile. That’s it.With Python, you have to ask: Does Python exist on this machine? Is it the right version? You’ll need a virtual environment so you don’t pollute system Python. You need third-party libraries installed via a package manager; and please friends, remember that we don’t let friends use pip. Use Poetry or uv. Pip is so bad that I’d honestly argue that Bash not having a package manager is better than Python having pip. At least Bash doesn’t pretend to manage dependencies well.
If you want something simple, something that can run on practically any Unix-based machine without setup, Bash is your answer. Even Windows can handle it these days through WSL, though you’re jumping through a few hoops.
TLDR
The decision is actually pretty straightforward:
- Use Bash when you’re gluing together system commands, the logic is linear, it’s under 50 lines, and portability matters.
- Use Python when you’re parsing structured data, need error handling, have branching logic, want proper tests, or the script is going to grow.
If you’re reaching for
jq, writing nestedifstatements, or the script is getting long enough that you’re losing track of what it does… it’s time for Python.I think in a future post we might look at when Go makes sense over Bash. There’s a lot to cover there about compiled binaries, but for now, hopefully this helps you make the call next time you’re wondering what to start your scripting with.
/ DevOps / Programming / Python / Bash / Scripting
-
How Python and Kotlin provide structured concurrency out of the box while Go achieves the same patterns explicitly using errgroup, WaitGroup, and context.
/ Programming / Golang / Python / links
-
I built a hacker terminal typing game. Type Python symbols to “decrypt” corrupted code, hex becomes assembly, then pseudocode, then real source. Mess up and the screen glitches. A typing tutor for programmers that doesn’t feel like one.
/ Gaming / Programming / Python
-
Stop Using pip. Seriously.
If you’re writing Python in 2026, I need you to pretend that pip doesn’t exist. Use Poetry or uv instead.
Hopefully you’ve read my previous post on why testing matters. If you haven’t, go read that first. Back? Hopefully you are convinced.
If you’re writing Python, you should be writing tests, and you can’t do that properly with pip. It’s an unfortunate but true state of Python right now.
In order to write tests, you need dependencies, which is how we get to the root of the issue.
The Lock File Problem
The closest thing pip has to a lock file is
pip freeze > requirements.txt. But it just doesn’t cut the mustard. It’s just a flat list of pinned versions.A proper lock file captures the resolution graph, the full picture of how your dependencies relate to each other. It distinguishes between direct dependencies (the packages you asked for) and transitive dependencies (the packages they pulled in). A
requirements.txtdoesn’t do any of that.Ok, so? You might be asking yourself.
It means that you can’t guarantee that running
pip install -r requirements.txtsix months or six minutes from now will give you the same copy of all your dependencies.It’s not repeatable. It’s not deterministic. It’s not reliable.
The one constant in Code is that it changes. Without a lock file, you’re rolling the dice every time.
Everyone Else Figured This Out
Every other modern language ecosystem “solved” this problem years ago:
- JavaScript has
package-lock.json(npm) andpnpm-lock.yaml(pnpm) - Rust has
Cargo.lock - Go has
go.sum - Ruby has
Gemfile.lock - PHP has
composer.lock
Python’s built-in package manager just… doesn’t have this.
That’s a real problem when you’re trying to build reproducible environments, run tests in CI, or deploy with any confidence that what you tested locally is what’s running in production.
What to Use Instead
Both Poetry and uv solve the lock file problem and give you reproducible environments. They’re more alike than different — here’s what they share:
- Lock files with full dependency resolution graphs
- Separation of dev and production dependencies
- Virtual environment management
pyproject.tomlas the single config file- Package building and publishing to PyPI
Poetry is the more established option. It’s at version 2.3 (released January 2026), supports Python 3.10–3.14, and has been the go-to alternative to pip for years. It’s stable, well-documented, and has a large ecosystem of plugins.
uv is the newer option from Astral (the team behind Ruff). It’s written in Rust and is 10–100x faster than pip at dependency resolution. It can also manage Python versions directly, similar to mise or pyenv. It’s currently at version 0.10, so it hasn’t hit 1.0 yet, but gaining adoption fast.
You can’t go wrong with either. Pick one, use it, and stop using pip.
/ DevOps / Programming / Python
- JavaScript has
-
Switching to mise for Local Dev Tool Management
I’ve been making some changes to how I configure my local development environment, and I wanted to share what I’ve decided on.
Let me introduce to you, mise (pronounced “meez”), a tool for managing your programming language versions.
Why Not Just Use Homebrew?
Homebrew is great for installing most things, but I don’t like using it for programming language version management. It is too brittle. How many times has
brew upgradedecided to switch your Python or Node version on you, breaking projects in the process? Too many, in my experience.mise solves this elegantly. It doesn’t replace Homebrew entirely, you’ll still use that for general stuff but for managing your system programming language versions, mise is the perfect tool.
mise the Great, mise the Mighty
mise has all the features you’d expect from a version manager, plus some nice extras:
Shims support: If you want shims in your bash or zsh, mise has you covered. You’ll need to update your RC file to get them working, but once you do, you’re off to the races.
Per-project configuration: mise can work at the application directory level. You set up a
mise.tomlfile that defines its behavior for that specific project.Environment management: You can set up environment variables directly in the toml file, auto-configure your package manager, and even have it auto-create a virtual environment.
It can also load environment variables from a separate file if you’d rather not put them in the toml (which you probably want if you’re checking the file in).
It’s not a package manager: This is important. You still need poetry or uv for Python package management. As a reminder: don’t ever use pip. Just don’t.
A Quick Example
Here’s what a
.mise.tomlfile looks like for a Python project:[tools] python = "3.12.1" "aqua:astral-sh/uv" = "latest" [env] # uv respects this for venv location UV_PROJECT_ENVIRONMENT = ".venv" _.python.venv = { path = ".venv", create = true }Pretty clean, right? This tells mise to use Python 3.12.1, install the latest version of uv, and automatically create a virtual environment in
.venv.Note on Poetry Support
I had to install python from source using mise to get poetry working. You will want to leave this setting to be true. There is some problem with the precompiled binaries they are using.
You can install global python packages, like poetry, with the following command:
mise use --global poetry@latestYes, It’s Written in Rust
The programming veterans among you may have noticed the toml configuration format and thought, “Ah, must be a Rust project.” And you’d be right. mise is written in Rust, which means it’s fast! The project is stable, has a ton of GitHub stars, and is actively maintained.
Task Runner Built-In
One feature I wasn’t expecting: mise has a built-in task runner. You can define tasks right in your
mise.toml:[tasks."venv:info"] description = "Show Poetry virtualenv info" run = "poetry env info" [tasks.test] description = "Run tests" run = "poetry run pytest"Then run them with
mise run testormise r venv:info.If you’ve been putting off setting up Make for a project, this is a compelling alternative. The syntax is cleaner and you get descriptions for free
I’ll probably keep using Just for more complex build and release workflows, but for simple project tasks, mise handles it nicely. One less tool to install.
My Experience So Far
I literally just switched everything over today, and it was a smooth process. No too major so far. I’ll report back if anything breaks, but the migration from my previous setup was straightforward.
Now, I need to get the other languages I use, like Go, Rust, and PHP setup and moved to mise. Having everything consolidated into one tool is going to be so nice.
If you’re tired of Homebrew breaking your language versions or juggling multiple version managers for different languages, give mise a try.
The documentation is solid, and the learning curve is minimal.
/ DevOps / Tools / Development / Python
-
JavaScript Still Doesn't Have Types (And That's Probably Fine)
Here’s the thing about JavaScript and types: it doesn’t have them, and it probably won’t any time soon.
Back in 2022, there was a proposal to add TypeScript-like type syntax directly to JavaScript. The idea was being able to write type annotations without needing a separate compilation step. But the proposal stalled because the JavaScript community couldn’t reach consensus on implementation details.
The core concern? Performance. JavaScript is designed to be lightweight and fast, running everywhere from browsers to servers to IoT devices. Adding a type system directly into the language could slow things down, and that’s a tradeoff many aren’t willing to make.
So the industry has essentially accepted that if you want types in JavaScript, you use TypeScript. And honestly? That’s fine.
TypeScript: JavaScript’s Type System
TypeScript has become the de facto standard for typed JavaScript development. Here’s what it looks like:
// TypeScript Example let name: string = "John"; let age: number = 30; let isStudent: boolean = false; // Function with type annotations function greet(name: string): string { return `Hello, ${name}!`; } // Array with type annotation let numbers: number[] = [1, 2, 3]; // Object with type annotation let person: { name: string; age: number } = { name: "Alice", age: 25 };TypeScript compiles down to plain JavaScript, so you get the benefits of static type checking during development without any runtime overhead. The types literally disappear when your code runs.
The Python Parallel
You might be interested to know that the closest parallel to this JavaScript/TypeScript situation is actually Python.
Modern Python has types, but they’re not enforced by the language itself. Instead, you use third-party tools like mypy for static analysis and pydantic for runtime validation. There’s actually a whole ecosystem of libraries supporting types in Python in various ways, which can get a bit confusing.
Here’s how Python’s type annotations look:
# Python Example name: str = "John" age: int = 30 is_student: bool = False # Function with type annotations def greet(name: str) -> str: return f"Hello, {name}!" # List with type annotation numbers: list[int] = [1, 2, 3] # Dictionary with type annotation person: dict[str, int] = {"name": "Alice", "age": 25}Look familiar? The syntax is surprisingly similar to TypeScript. Both languages treat types as annotations that help developers and tools understand the code, but neither strictly enforces them at runtime (unless you add additional tooling).
What This Means for You
If you’re writing JavaScript, stop, and use TypeScript. It’s mature and widely adopted. Now also you can run TypeScript directly in some runtimes like Bun or Deno.
Type systems were originally omitted from many of these languages because the creators wanted to establish a low barrier to entry, making it significantly easier for people to adopt the language.
Additionally, computers at the time were much slower, and compiling code with rigorous type systems took a long time, so creators prioritized the speed of the development loop over strict safety.
However, with the power of modern computers, compilation speed is no longer a concern. Furthermore, the type systems themselves have improved significantly in efficiency and design.
Since performance is no longer an issue, the industry has shifted back toward using types to gain better structure and safety without the historical downsides.
/ Programming / Python / javascript / Typescript
-
A daily column with insights, observations, tutorials and best practices on python and data science. Read by industry professionals at big tech, startups, and engineering students.
-
Learning to Program in 2026
If I had to start over as a programmer in 2026, what would I do differently? This question comes up more and more and with people actively building software using AI, it’s as relevant as ever.
Some people will tell you to pick a project and learn whatever language fits that project best. Others will push JavaScript because it’s everywhere and you can build just about anything with it. Both are reasonable takes, but I do think there’s a best first language.
However, don’t take my word for it. Listen to Brian Kernighan. If you’re not familiar with the name, he co-authored The C Programming Language back in 1978 and worked at Bell Labs alongside the creators of Unix. Oh also, he is a computer science Professor at Princeton. This man TAUGHT programming to generations of computer scientists.
There’s an excellent interview on Computerphile with Kernighan where he makes a compelling case for Python as the first language.
Why Python?
Kernighan makes three points that you should listen to.
First, the “no limitations” argument. Tools like Scratch are great for kids or early learners, but you hit a wall pretty quickly. Python sits in a sweet spot—it’s readable and approachable, but the ecosystem is deep enough that you won’t outgrow it.
Second, the skills transfer. Once you learn the fundamentals—loops, variables, data structures—they apply everywhere. As Kernighan puts it: “If you’ve done N programming languages, the N+1 language is usually not very hard to get off the ground.”
Learning to think in code matters more than any specific syntax.
Third, Python works great for prototyping. You can build something to figure out your algorithms and data structures, then move to another language depending on your needs.
Why Not JavaScript?
JavaScript is incredibly versatile, but it throws a lot at beginners. Asynchronous behavior, event loops,
thisbinding, the DOM… and that’s a lot of cognitive overhead when you’re just trying to grasp what a variable is.Python’s readable syntax lets you focus on learning how to think like a programmer. Fewer cognitive hurdles means faster progress on the fundamentals that actually matter.
There’s also the type system. JavaScript’s loose equality comparisons (
==vs===) and automatic type coercion trip people up constantly.Python is more predictable. When you’re learning, predictable is good.
The Path Forward
So here’s how I’d approach it: start with Python and focus on the basics. Loops, variables, data structures.
Get comfortable reading and writing code. Once you’ve got that foundation, you can either go deeper with Python or branch out to whatever language suits the projects you want to build.
The goal isn’t to master Python, it’s to learn how to think about problems and express solutions in code.
That skill transfers everywhere, including reviewing AI-generated code in whatever language you end up working with.
There are a ton of great resources online to help you learn Python, but one I see consistently is Python for Everyone by Dr Chuck.
Happy coding!
/ Programming / Python / learning
-
When do you think everyone will finally agree that Python is Python 3 and not 2? I know we aren’t going to get a major version bump anytime soon, if ever again, but we really should consider putting uv in core… Python needs modern package management baked in.
/ Programming / Python