🧵Startup Architecture Antipatterns: The "Just One More Thread" Trap Your MVP needs to process some data, and spinning up additional infrastructure feels like a lift. So you run the work on a quick background thread in your API server. Seems harmless enough, right? Fast forward three months: "Can we just add image processing here?" "Let's run that ML model in the background..." "Just one more CSV export..." Suddenly your API server is responsible for multiple compute-intensive and long-running jobs. Each one a ticking time bomb waiting to explode your response times (or worse, your entire server!). The Painful Truth: Every "temporary" background thread you spawn is technical debt with compound interest. That 2-second task becomes 20 seconds with the wrong payload or DB bloat, and now your entire platform is down because your thread pool is drowning in long-running processes. Even worse, pathological payloads can OOM your server, leading to that dreaded 502 for all your customers. These issues often don't show up in testing. They lurk in production, waiting for that perfect storm of concurrent requests. You try to autoscale to spread the load... but with no control over routing, this only gets you so far. Then you scramble to rearchitect, but it takes days to weeks (or you cut new corners until the next incident). ✅ The Right Way? Separate Background Workers From Day One! • Your API is an INTERFACE, not the program—it should be primarily responsible for network requests, auth, and work routing • Queue background tasks immediately • Let dedicated workers handle the heavy lifting • Scale workers independently based on load and let your queue retry on a new worker on failure 💡 Key takeaway: If your API is down, your entire application is down... keep it thin and limited to network and IO bound requests! What "temporary" solution in your codebase has outstayed its welcome?
Common Anti-Patterns in Software Development
Explore top LinkedIn content from expert professionals.
Summary
Common anti-patterns in software development refer to recurring, suboptimal coding practices or architectural decisions that often lead to inefficiencies, reduced maintainability, and increased technical debt. Identifying and avoiding these pitfalls can result in better performance, reliability, and scalability for your projects.
- Separate responsibilities early: Avoid combining background tasks, such as intensive processing, within your API server; use dedicated workers and queues to handle such tasks independently.
- Keep logic isolated: Prevent core application logic from becoming cluttered by isolating AI components or external service interactions in separate, well-defined structures.
- Resist premature abstraction: Only introduce complex abstractions when there’s a proven need; over-engineering for hypothetical future cases can create unnecessary complexity and slow down progress.
-
-
Effective AI Tip #1: Build Bulkheads Around Your AI Calls! AI components (especially LLMs) have unique failure modes and operational needs (retries, specific logging, structured output handling) compared to typical application code. Mixing them directly creates brittle systems that are hard to manage. The Common Anti-Pattern: We often see code like the BEFORE section in the code image. Why this is problematic: - Reliability: Where do you add retries just for `ai_service.completion` without cluttering `generate_response`? How do you handle specific AI errors gracefully? - Logging/Observability: Need to log the exact prompt, raw response, latency, token counts? You have to manually add that logging *here*, and potentially everywhere else `ai_service` is called. - Structured Output: What if you need JSON, not just a string? You parse the `response_text` *after* the call, mixing parsing logic with core application flow. Validation becomes an afterthought. - Maintenance: Changing the underlying AI model, adding Chain-of-Thought prompting, or needing different parsing logic requires modifying `generate_response` directly, increasing the risk of breaking unrelated business logic. The Better Way: The Bulkhead Pattern Isolate the AI call behind a dedicated function or class. This acts as a "bulkhead," containing the AI's specific needs and protecting the rest of your application. Bulkheads are named after sectioned partitions of a ship's hull, which prevents damage from one section causing water leakage to another. You can see the principle implemented with mirascope in the AFTER section in the code image. Why this "Bulkhead" is better - Targeted Reliability: Need retries? Add a retry decorator (`@retry(...)`) to `generate_response_llm` – it doesn't touch `generate_response`. - Clean Structured Output: Define the `GenResponse` model. The framework (or your custom wrapper) handles parsing and validation *within the bulkhead*. `generate_response` receives clean, validated data. - Centralized Control: Want to add Chain-of-Thought? Update the `PROMPT_TEMPLATE` and maybe `GenResponse`. Need better parsing? Add a parsing decorator to `generate_response_llm`. Changes are localized. - Simplified Observability: Instrument `generate_response_llm` once (e.g., using tracing tools like `lilypad` or manual logging) to capture all AI interaction details (prompt, raw/parsed response, latency, cost) consistently. The Takeaway Don't let AI calls bleed into your core logic. **Wrap them in dedicated functions/classes (Bulkheads)** to handle their unique reliability, input/output, and observability needs. This isolation makes your system more robust, maintainable, and easier to evolve.
-
Software Engineering Anti-patterns to Avoid 🚫 Recognizing and avoiding anti-patterns can significantly improve your software development process. Here are some common ones to watch out for: Spaghetti Code: Unstructured and difficult-to-maintain code. Aim for clear, modular code with proper documentation. God Object: Overloaded objects with too many responsibilities. Follow the Single Responsibility Principle (SRP) to keep objects focused. Big Ball of Mud: Lack of design leading to a disorganized system. Invest in proper architectural planning and design. God Class: A single class handling all control in a program. Distribute control across multiple classes for better maintainability. Magic Numbers: Unique values with an unexplained meaning. Use named constants or configuration files for clarity. Poltergeists: Ephemeral controller classes that only exist to invoke other methods. Avoid unnecessary layers and keep your design simple. Gold Plating: Adding unnecessary features beyond requirements. Focus on delivering value and avoid overengineering. Copy-Paste Programming: Duplicating code instead of reusing components. Promote code reuse through functions and libraries. Shotgun Surgery: Making a change requires altering many parts of the system. Strive for high cohesion and low coupling. Reinventing the Wheel: Creating custom solutions for problems with existing, proven solutions. Leverage existing libraries and frameworks. Not-Invented-Here Syndrome: Rejecting external solutions in favor of homegrown ones. Be open to adopting third-party solutions when appropriate. Avoid these pitfalls to enhance code quality and maintainability! 🚀 Comment below what else did i miss. #SoftwareEngineering #Antipatterns #CodeQuality
-
Premature abstraction is the silent killer of team velocity. Every “future-proof” layer is a tax on everyone’s time, now and forever. It’s easy to justify an extra interface or generic module in the name of flexibility. But every hypothetical scenario baked into the codebase becomes real debt: more documentation, slower onboarding, and countless hours spent navigating complexity that may never pay off. The irony is that efforts to “future-proof” often slow teams down in the present, trading current momentum for imagined needs that might never materialize. Sacrificing speed in pursuit of an abstract ideal often leads to solutions that are slower and less practical. Sometimes the simplest solution is the only one that scales with reality.
-
Anti-patterns for making software: - no CI pipelines - CI pipelines that are more than 30 min - no approvers for any repos - more than 1 approver for every PR - everyone committing directly master w/o feature flags - using gitflow - inventing a new sort algorithm for your system that will never have more 1k records - one feature change requiring touching 10+ repositories in the correct order - AbstractBaseClassPatternFactoryReactor class subclassed just once - 50k line files (or functions 🫠) - making an ERD for a 1 hour change - not making an ERD for a 400 hour feature - table names that are over the wrap limit of a modern IDE - no logging - very verbose logging (in many ways worse than no logging) I don't believe there are such a thing as "best practices", but there are bad ones. These are some I came up with. What else should have made the cut?
-
There are many anti-patterns in microservices. But, one of the worst is Content Coupling. This problem happens when an external service directly manipulates another service's database. 𝗟𝗲𝘁’𝘀 𝗿𝗲𝘃𝗶𝗲𝘄 𝗮𝗻 𝗲𝘅𝗮𝗺𝗽𝗹𝗲. 𝗪𝗵𝗲𝗿𝗲 𝘆𝗼𝘂 𝗵𝗮𝘃𝗲: - Order Procesor - Orders Service - Shipping Service - Orders Database In this case, the shipping service changes the order's status directly into the DB. What happens if there is a set of logic controlling which status of the order you can apply at any time? This might sound more direct and optimal, but have some serious problems: 1. The lines of ownership become less clear. 2. You will have to at least duplicate business logic and maintain it. 3. When changing the Order service, we now have to be extremely careful about changing the table. 4. If we make any changes, you will likely have to coordinate deployments. The easy fix is having the Shipping send requests to the Orders service. 𝗧𝗵𝗲 𝗯𝗲𝗻𝗲𝗳𝗶𝘁𝘀 𝗮𝗿𝗲: The Orders Service alone manages and executes business logic, ensuring consistency and integrity. The Shipping Service doesn't need to know how the Orders Service processes requests. Each service can evolve independently. When you allow an outside party to access your database, the database becomes part of that external contract. You've lost the ability to define what is shared (and thus cannot be changed easily). Avoid Content Coupling at all costs; it is No Bueno!