8 Skills I Ignored Until They Started Making Me Money
By My Code Diary
I spent two years writing Python the way most people drive, technically functional, occasionally dangerous, and completely unaware of what the dashboard actually does.
I was getting things done. Scripts ran. Clients were happy. But something was off. Other developers were closing bigger contracts, building faster, and somehow their code just looked different. Cleaner. More intentional. Like someone who actually cared lived inside the repository.
The embarrassing part? The skills separating us were not exotic. They were not machine learning PhDs or distributed systems mastery. They were things I had scrolled past in tutorials, mentally labeled “I’ll learn this someday,” and then completely ignored for years.
That someday eventually arrived not by choice, but because a client’s production system broke at 2 AM and the fix required a skill I did not have. There is nothing like a crisis invoice to accelerate learning.
Here are the 8 skills I kept dismissing until they quietly started paying my bills.
1. Writing Proper Decorators
I knew decorators existed. I used @staticmethod and copy-pasted @login_required from Stack Overflow like everyone else. But writing one from scratch? I had mentally filed that under “advanced Python stuff for people with computer science degrees.”
Then I took on a project where I needed to add retry logic, logging, and timing to about 40 different API functions. Without decorators, that meant 40 copy-paste jobs of the same boilerplate. With decorators, it meant writing the logic once.
import time
import functools
def retry(times=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == times - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=3, delay=2)
def call_flaky_api(endpoint):
# your API call here
pass
One decorator. Forty functions protected. The client never noticed a failed API call again. I noticed the invoice.
Pro Tip: functools.wraps is not an optional decoration; it preserves the original function’s name and docstring, which matters the moment someone tries to debug your code at midnight.
2. Context Managers
For years, I wrote file = open("data.txt") and hoped for the best. Sometimes I remembered to call file.close(). Often, I did not. My scripts leaked file handles like a broken pipe, and I had no idea.
The with statement felt like syntax sugar to me, nice but unnecessary. Then I started building tools that opened database connections, acquired locks, and managed temporary files. Suddenly, “unnecessary” became “the only thing standing between me and a deadlocked database at 3 AM.”
from contextlib import contextmanager
import sqlite3
@contextmanager
def get_db_connection(db_path):
conn = sqlite3.connect(db_path)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
with get_db_connection("mydb.sqlite") as conn:
conn.execute("INSERT INTO logs VALUES (?)", ("event",))
Resources open, resources close. Automatically. Even when things go wrong. This is not a nice-to-have; it is the difference between a professional tool and a script that quietly corrupts data.
3. Automation with schedule and cron
Here is a skill I ignored for an embarrassing reason: I thought automation was only for DevOps engineers. Real programmers, I told myself, wrote interactive applications.
Then a client asked me to build a report that ran every morning at 7 AM, scraped three data sources, compiled the results, and emailed it to their team. Automatically. Every day. Without anyone pressing a button.
import schedule
import time
def generate_morning_report():
# scrape, compile, email
print("Report sent.")
schedule.every().day.at("07:00").do(generate_morning_report)
while True:
schedule.run_pending()
time.sleep(60)
That script ran for 14 months without a single human intervention. The client renewed the contract twice. Automation is not a DevOps thing; it is a “get paid while you sleep” thing.
4. Environment Variables and .env Files
I will admit something embarrassing. I once pushed an API key to a public GitHub repository. noticed within 20 minutes. GitHub’s automated scanner noticed within 4.
Hardcoding secrets into scripts is the programming equivalent of leaving your house key under a mat that says “KEY IS HERE.” It feels fine until the moment it is catastrophically not fine.
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
db_password = os.getenv("DB_PASSWORD")
Your .env file lives locally. It never touches version control. Every junior developer learns this the hard way. Learn it here instead.
5. Generators for Large Data
I once wrote a script that loaded 800,000 rows of CSV data into a list before processing it. My laptop sounded like it was trying to achieve lift-off. The script eventually finished after exhausting 12 GB of RAM and testing my patience for 40 minutes.
A senior developer looked at my code, replaced the list with a generator, and the same operation used 200 MB and finished in 3 minutes.
def read_large_csv(filepath):
with open(filepath, "r") as f:
for line in f:
yield line.strip().split(",")
for row in read_large_csv("massive_data.csv"):
process(row)
Generators do not load everything into memory at once. They produce one item at a time, on demand. For large datasets, this is not an optimization; it is the only option that works.
6. Logging Instead of Print Statements
Every Python script I wrote in my first two years had print() statements scattered through it like breadcrumbs in a forest. Debugging meant reading hundreds of console outputs and guessing which one mattered.
Production code cannot work this way. When a script runs on a server at 3 AM and fails silently, you need structured logs with timestamps and severity levels, not a trail of print statements that no one is watching.
import logging
logging.basicConfig(
filename="app.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("Process started")
logging.warning("Rate limit approaching")
logging.error("API call failed: %s", error_message)
The first time I debugged a production issue using proper logs instead of print statements, I solved it in 8 minutes. The previous similar issue had taken me 3 hours with print debugging. That time difference is worth real money.
7. Type Hints
Type hints felt optional to me for a long time. Python runs fine without them. Why bother?
The answer arrived when I joined a team project and spent 45 minutes tracing a bug that existed because one function expected a list of strings and received a list of integers. A type hint would have made that impossible or, at a minimum, immediately visible.
from typing import Optional
def process_user_data(
user_id: int,
name: str,
email: Optional[str] = None
) -> dict:
return {"id": user_id, "name": name, "email": email}
Type hints are documentation that enforces itself. IDEs use them for autocompletion and error detection. Teams use them to understand code without reading every line. After adding them to a client codebase, code review time dropped by roughly 30% they could see intent without interrogating the implementation.
8. Building Things That Can Be Reused
This is the least glamorous skill on the list and the one that has made me the most money.
For most of my early career, I wrote scripts that solved one specific problem once. Need to scrape a website? New script. Need to process PDFs? New script. Every project started from a blank file and ended with something that could never be touched again.
At some point, I started building small, reusable modules instead. A PDF reader. An email sender. A rate-limited API wrapper. Not libraries, just clean, documented functions I could drop into any new project.
# pdf_utils.py — reusable across every project
def extract_text_from_pdf(pdf_path: str, pages: list = None) -> str:
import fitz
doc = fitz.open(pdf_path)
target_pages = pages or range(len(doc))
return "\n".join(doc[i].get_text() for i in target_pages)
The third time I used that PDF utility, the project took half the time. The fifth time, I finished a day early and billed the same rate. Reusability is the compound interest of programming.
The Pattern I Missed for Two Years
Looking back at this list, none of these skills are exotic. They are not cutting-edge research topics or niche specializations. They are foundational practices that separate code that works from code that works reliably, efficiently, and professionally.
The problem was that I was always optimizing for getting things working rather than getting things right. Good enough shipped. Good enough, got paid, and Good enough is a trap.
The best investment I made was not learning a new framework or a new AI tool. It was going back to the fundamentals I had skipped and doing them properly.
Start with the skill on this list that makes you most uncomfortable. That discomfort is information.
Found this useful? The best way to lock in a skill is to immediately build something with it. Pick one of the eight above and spend the next two hours applying it to a problem you are already working on.



