Loading
Pavlo Mashchak

Full-Stack Engineer

AI Automation Engineer

ETL Data Developer

MVP Prototyping

Cloud Deployments

  • About
  • Works
  • Services
  • Resume
  • Skills
  • Blog
  • Contact
Pavlo Mashchak

Full-Stack Engineer

AI Automation Engineer

ETL Data Developer

MVP Prototyping

Cloud Deployments

Download CV

Recent Posts

  • Limitations of LLMs and agents
  • Programming patterns and seniority in software development.
  • Twenty years of software transformation – what’s truly changed?

Recent Comments

  1. Ryan Adlard on Limitations of LLMs and agents
  2. Ryan Adlard on Twenty years of software transformation – what’s truly changed?
  3. James Rodri on Twenty years of software transformation – what’s truly changed?
  4. John Doe on Twenty years of software transformation – what’s truly changed?

Archives

  • November 2025
  • October 2025
  • September 2025

Categories

  • Code
  • Design
Blog Post

Programming patterns and seniority in software development.

October 9, 2025 Code by Pavlo Mashchak
Programming patterns and seniority in software development.

Having worked with enterprises, startups, and small family-run businesses, I’ve seen clear foundational differences that separate senior programmers from junior ones.
In this article, I’ll unpack that distinction and explore the deeper principles that drive professional growth in software engineering.
The answer, surprisingly, lies not in experience or technology stacks—but in understanding the basics of software design and the timeless paradigms of programming.

Object-Oriented Programming (OOP) traces its origins to the 1960s with Simula 67, created by Norwegians Ole-Johan Dahl and Kristen Nygaard. Perhaps it’s no coincidence that many Scandinavian developers—like Trygve Reenskaug (inventor of MVC) and Linus Torvalds—played foundational roles in modern computing.

OOP gained traction with C++ in the 1980s and matured with Java in the 1990s, when large enterprises faced the challenge of managing increasingly complex codebases. Concepts like abstraction, encapsulation, and inheritance emerged to handle this complexity, allowing systems to model real-world entities such as User, Order, and Invoice.

By isolating functionality and hiding implementation details, developers could protect data integrity and extend functionality through polymorphism. This shift enabled reusable, maintainable libraries—something procedural code could rarely achieve.


Why patterns matter?

Is understanding OOP and its related paradigms like SOLID really what defines seniority?
In short—yes. Everything essential to building good software has already been invented.

Objects exist in complex ecosystems—they’re created, modified, and destroyed while interacting through defined relationships. These structured interactions are captured by software design patterns.

While there are many categories of patterns—architectural (e.g., MVC), system (e.g., scalability), enterprise integration (e.g., message queues, Kafka), or domain-driven—the core always begins with code-level design patterns. Without that foundation, larger abstractions collapse. Understanding these basic building blocks is what transforms code from functional into engineered.


Re-invention of a wheel.

Most design patterns already exist—and the gap between junior and senior engineers often lies in whether someone keeps “reinventing the wheel.”

A junior developer tends to build custom, overcomplicated solutions, proudly declaring “but it works!”—often defensive of fragile, one-off designs. This approach leads to bloated, hard-to-extend, and slow systems.

A senior developer, however, does the opposite: they reuse proven patterns, architectures, and principles. As T.S. Eliot once wrote,

“Immature poets imitate; mature poets steal.”

Seniority isn’t about years of experience—it’s about understanding the cost of not following best practices. Ignoring design principles leads to tech debt, spaghetti code, and rigid systems. True professionals stay open to improvement because they know that patterns exist to make software resilient, not to restrict creativity.


Adhering to the framework.

Another key distinction: junior engineers often cling to specific frameworks or technologies, branding them as “the best.” They ride the hype cycle until the tool becomes obsolete.

Senior engineers, in contrast, focus on concepts—the underlying architecture and principles that transcend tools. Frameworks evolve, but design fundamentals remain constant.

Patterns act like an anatomy textbook for software development. They reveal how systems are built, interact, and evolve. Without understanding these fundamentals, progress is limited, no matter how modern the stack appears.


The big Why?

Why do design patterns matter so much? Because they’re the difference between structured, scalable systems and spaghetti code that cripples pipelines with long builds and unstable deployments.

To design effectively, one must grasp UML diagrams—the blueprints of software:

  • Class Diagrams define structure.

  • Sequence Diagrams show interaction and message flow.

Understanding design patterns allows you to see how relationships and dependencies shape code.
Without this, concepts like ORM (ActiveRecord, SQLAlchemy, MongoDB mappers) or MVC remain superficial—because their strength lies in separation of concerns and object relationships.

There are three main categories of design patterns:

  • Creational – object creation and instantiation.

  • Structural – object composition and hierarchy.

  • Behavioral – object communication and collaboration.


Basics of OOP

Encapsulation: Bundling data and methods into a single unit, hiding internal logic and exposing only what’s necessary.

Inheritance: Creating a hierarchy where base classes define shared behavior, and child classes specialize or override it.

Abstraction and Interfaces: Defining clear contracts for implementation. Interfaces describe what must be done; classes realize how it’s done, ensuring consistency across implementations.

Loose Coupling: Reducing interdependencies between components.
For instance, if Class A depends on Class B, which depends on Class C, you should be able to change B without affecting others.

Separation of Concerns: Each part of a system should handle one distinct responsibility. MVC frameworks exemplify this by dividing Presentation, Logic, and Data layers.

Law of Demeter: Objects should only communicate with their direct “friends.”
In other words, A talks to B, B talks to C, but A should never directly talk to C.
This artificial limitation preserves modularity and prevents tangled dependencies.

SOLID principles—Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion—embody these ideals, guiding developers toward modular, reusable, and maintainable systems.


SOLID

Single Responsibility

A class should have one and only one reason to change.

# Printer only prints, Report only reports
class Report:
    def __init__(self, text):
        self.text = text

    def format(self):
        return self.text.upper()

class Printer:
    def print(self, text):
        print(text)

r = Report("monthly report")
Printer().print(r.format())
 

Open-Closed Design

Classes should be open for extension but closed for modification.

🧩 Add new behavior by extending, not modifying. 

class Shape: 
    def area(self): pass

class Square(Shape):
    def __init__(self, s):
        self.s = s
    def area(self):
        return self.s ** 2

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14 * self.r ** 2

print(sum(s.area() for s in [Square(2), Circle(3)]))

Liskov Substitution

Derived classes should seamlessly replace their base classes without altering functionality or expectations.

👉 Subclasses should work anywhere the base class works.

class Bird:
    def move(self):
        return "flying"

class Penguin(Bird):
    def move(self):
        return "swimming"

for b in [Bird(), Penguin()]:
    print(f"{b.__class__.__name__} is {b.move()}")

Interface Segregation

Clients shouldn’t be forced to depend on interfaces they don’t use.
Instead of one bloated interface, use multiple specific ones.

🚫 Don’t force classes to implement unused methods.

class Printer:
    def print(self):
        print("printing...")

class Scanner:
    def scan(self):
        print("scanning...")

Dependency Inversion

High-level modules should depend on abstractions, not concrete implementations.
This ensures flexibility and easier testing.

🔁 High-level code depends on abstractions, not details.

class PaymentGateway:
    def pay(self, amount):
        pass

class Stripe(PaymentGateway):
    def pay(self, amount):
        print(f"Stripe paid ${amount}")

class Checkout:
    def __init__(self, gateway):
        self.gateway = gateway
    def process(self):
        self.gateway.pay(100)

The basics lead to complex and deep.

We’ve explored how seniority in software engineering grows from understanding fundamentals—not from mastering the latest frameworks or languages.

Modern systems are built atop patterns that encapsulate decades of collective learning.
Imagine an application juggling multiple dependencies: external APIs, databases, event streams, and real-time updates. To ensure it’s maintainable and resilient, we apply proven patterns like:

  • Saga Orchestrator, where each action has a compensating rollback.

  • Facade, simplifying interactions across multiple services.

  • Distributed Lock, safeguarding multi-step, data-sensitive operations.

There’s no single “perfect” design—just patterns that fit best for a given context. Seniority lies in knowing which to apply and when, often blending several to achieve balance between flexibility and control.


Thoughts and comments are welcomed, let me know if this is useful and engaging.

Share:
Tags: clean codedesign patternspython programmingsoftware architecturesoftware developmentsolid

Post navigation

Prev
Next
Write a comment Cancel Reply

© 2025 Pavlo Mashchak — All rights reserved.
Based in Orlando, FL — Available for remote contracts worldwide.