The most basic advice given to software engineers is to keep things simple, yet often we end up doing exactly the opposite. - If you could solve it with 1 service, why build 10. - If you could pass 3 fields, why create a giant context object. - If you could keep the flow in one place, why make people jump across 7 abstractions. - If you could store the truth once, why copy it into 4 layers and pray they stay in sync. This sounds obvious. Still, most codebases drift the other way. More wrappers. More indirection. More hidden state. More “future-proofing” for futures that never come. And then one day, a simple change takes 3 days because nobody knows where the real logic lives. A lot of engineers confuse sophistication with quality. But in practice, good judgment often looks boring: [1] Choosing one clean path over five flexible ones & keeping logic close to where it is used. [2] Not introducing a queue, cache, workflow engine, and agent loop for a problem that needs one function and a cron. [3] Saying no to abstractions until the duplication actually hurts. Optimizing for the next person who has to debug it, not for how smart it makes you look today. And yes, this applies to agent architectures too. If one agent with a clear scope can do the job, do not create five agents that pass context around like a corporate email chain. Each extra agent means: More layers to trace when something breaks. More state spread across prompts, tools, logs, and memory. More confusion for the human trying to debug it at 3 AM. Simple is not shallow. Simple is expensive. It takes taste to remove what is unnecessary. It takes confidence to not overbuild. It takes experience to know when flexibility is worth the cost and when it is just mental debt with better branding. A lot of software problems are not hard because the domain is hard. They are hard because we made them hard to think about. The craft is more than just building systems that work. It is building systems that are still understandable six months later. If you’re early in your career, please weave this into your skillset.
Software Design Patterns
Explore top LinkedIn content from expert professionals.
-
-
🪜 Designing Better Progress Steps UX. With practical techniques to help people navigate and complete complex forms with or without progress steps ↓ ✅ Progress steps break a long form into small, manageable parts. ✅ They show where users are and how much they have left to go. 🤔 But: they are often overlooked and don’t scale well on mobile. 🤔 Difficult to design for dynamic flows with conditional sections. ✅ For simple forms, always start without a progress indicator. ✅ Tell users what they need and how much time it will take. ✅ Show progress as “Step [X] out of [Y]” with a text label. ✅ Add a drop-down to support quick jumps between steps. 🚫 For complex forms, don’t rely on visual progress bar alone. ✅ Always include text labels under each step for easy, precise jumps. ✅ Underline labels to make it clear that users can use them to navigate. ✅ Design 6 states: incomplete, active, complete, error, disabled, warning. 🤔 You can rarely display 5+ progress steps on mobile. ✅ Keep active label visible but hide future and past steps. ✅ Show a Back link on the top, Next button at the bottom. ✅ For long forms, repeat the Back link at the bottom, too. ✅ On desktop, vertical progress steps often work better. ✅ Set up an overview page with links to single steps (“task list”). ✅ Allow users to expand and collapse all steps and sub-steps. ✅ Don’t forget to highlight error status in the progress step. Only few things are more frustrating than a progress bar that seems to be stuck. Complex forms often have conditional sections, so users end up going in circles, staying on the same step as they move between sections. It’s a common problem with horizontal layout, and a common reason why people leave. With a vertical layout, we can always show all sections with all sub-steps, explaining to users where they are and what’s coming up next. We can expand and collapse some steps and support fast navigation and quick jumps. We can also highlight all errors or missing data and explain what’s actually missing. A few smaller pages usually perform better than one long page. One column layout usually causes fewer errors as multi-column layout. Give users one column with vertical progress steps, and you might be surprised how much faster they get through the entire form, despite its complexity and length. Useful resources: Stepped Progression, by Goldman Sachs https://lnkd.in/eHbB5EFu Multi-Step Wizard, by GE HealthCare https://lnkd.in/ezA__z_E Task List Pattern, by Gov.uk https://lnkd.in/eb3PzTEJ Form Design: From Zero to Hero, by Adam Silver https://lnkd.in/eTBQxBXg Wizards Design Recommendations, by Raluca Budiu https://lnkd.in/eYYxUUDM Loading And Progress Indicators, by Taras Bakusevych https://lnkd.in/e5KFPiiq #ux #design
-
🧩 Distributed Transactions in Microservices – The Hidden Nightmare Let’s say you're building an e-commerce platform. You’ve broken down your monolith into decoupled microservices: 🛒 Order Service (PostgreSQL) 📦 Inventory Service (MongoDB) 💰 Payment Service (MySQL) 🚚 Shipping Service (Cassandra) Each service manages its own database. Life is good... until it’s not. 😱 The Problem A user places an order. Here's what needs to happen: Order is created ✅ Inventory is reserved ✅ Payment is deducted ✅ Shipping is scheduled ❌ Suddenly, the Shipping Service fails maybe due to a timeout, a network error, or an unavailable carrier. Now what? You’ve already deducted payment and reserved inventory. There’s no easy way to rollback across multiple databases and services. Distributed transactions are not natively supported across microservices. Using 2-phase commits? Forget it's slow, complex, and breaks under scale. 🔄 Enter the SAGA Pattern SAGA solves this by breaking the transaction into a sequence of local transactions – each with a compensating action in case something fails. Let’s walk through the same scenario with SAGA: 🛒 Order Service creates the order → emits OrderCreated event 📦 Inventory Service reserves items → emits InventoryReserved or rolls back via ReleaseInventory 💰 Payment Service deducts amount → emits PaymentSuccessful or compensates via RefundPayment 🚚 Shipping Service fails to schedule → emits ShippingFailed → triggers compensating actions: •RefundPayment •ReleaseInventory •CancelOrder Each service maintains local state and knows how to undo its step if needed. 💡 Two Approaches: Choreography – Services listen to and act on domain events. Orchestration – A central orchestrator coordinates the flow. 🧱 SAGA DB Design Behind the Scenes In an orchestration-based saga: saga_instance → tracks the entire flow saga_step_log → logs each step’s status (SUCCESS / FAILED / COMPENSATED) Each microservice stores its part of the transaction locally (idempotency + recovery) ✅ Key Benefits No need for distributed transactions Works with independent databases Allows graceful rollback with compensation Keeps systems event-driven and loosely coupled
-
Good onboarding feels like a dialogue. Bad onboarding feels like bureaucracy. Like filling forms at the dentist 👀 I did a teardown of Attio and found 6 design choices that can make long onboarding feel more like a two-way street. And ultimately, more delightful, more fun 🤸🏼 : ✅ Breathing space, whitespace + hierarchy so steps feel light ✅ Chunking to MAX 1–3 inputs per screen ✅ Smart defaults: pre-fills, dropdowns, autofill, location ✅ Personalisation: reflect user data back (in fun places) ✅ Reassurance; explain why you’re asking for info, show control ✅ Reward: visible progress, seeded data, alive workspace at the end The result is that you can have (almost) any length of onboarding, and still make it feel like a breeze. Steps are outlined in this one-pager - enjoy! (I know the irony of me not using enough whitespace in this visual, there was just too much to say 🫠) #product #growth
-
Whenever I start a new .NET project, I don’t begin with controllers or services. I begin by creating three simple files: .editorconfig Directory.Build.props Directory.Packages.props Over time, these became essential for me because they set the tone for the entire codebase long before the first feature is built. .editorconfig helps keep the code consistent. It defines the basics - indentation, spacing, naming rules, encoding - so the team writes code the same way. It reduces noise in pull requests and keeps reviews focused on the logic, not formatting. Directory.Build.props centralizes the shared project settings. Things like language version, nullable rules, warnings, and analyzers belong in one place instead of being copied across multiple csproj files. It keeps the solution clean and avoids configuration drifting over time. Directory.Packages.props manages all NuGet package versions. Having one place for dependencies makes it easier to upgrade, review, track, and avoid version conflicts. In larger systems, this alone prevents a lot of hidden problems. These may look like small details, but they add real structure from day one. They make onboarding easier, reduce unnecessary friction, and keep the project predictable as it grows. Starting strong is always easier than cleaning things up later. I’m curious - do you use these files as well? Or do you have your own way of setting the foundation for a new .NET project? #dotnet #softwareengineering #cleanarchitecture #bestpractices #devcommunity #csharp
-
Welcome to the sitcom-inspired guide to software design principles (SOLID): Single Responsibility Principle (SRP): Principle: Each class should have a single job or responsibility. Example: In "Modern Family," if Luke Dunphy were tasked with both completing his homework and doing household chores simultaneously, he'd likely perform poorly at both. By separating responsibilities, like focusing solely on homework during study time and solely on chores during chore time, he can maintain clarity and efficiency. Open/Closed Principle (OCP): Principle: Software entities should be open for extension but closed for modification. Example: In "Friends," Monica's apartment serves as a central setting. To introduce new characters without altering existing scenes, the creators add new elements to the apartment set rather than rearranging existing furniture, mirroring the principle in coding to maintain stability. Liskov Substitution Principle (LSP): Principle: Subtypes should be substitutable for their base types. Example: In "The Office," when Jim substitutes for Dwight, the office functions smoothly because Jim fulfills Dwight's role without disrupting workflow. Similarly, in coding, if a subclass can replace its superclass without altering the program's behavior, it adheres to LSP. Interface Segregation Principle (ISP): Principle: Clients should not be forced to depend on interfaces they don't use. Example: Just like Sheldon Cooper shouldn't be dragged to every social event by his friends (we all know he'd rather be doing physics), in coding, interfaces tailored to specific client needs to prevent unnecessary dependencies and promote modularity. Because, let's face it, nobody wants a grumpy physicist crashing their party or their code! Dependency Inversion Principle (DIP): Principle: High-level modules should not depend on low-level ones; both should depend on abstractions. Example: In "Breaking Bad," by abstracting Walter White's chemistry skills and Jesse Pinkman's Street knowledge to "chemist" and "distributor," they can adapt their partnership to new circumstances without compromising their operation, aligning with the DIP in coding. Happy coding, and may your programs be as entertaining as your favorite TV shows! #solidprinciples #softwaredesignpatterns #softwaredevelopment
-
Low Level Design (LLD) in Software Engineering =================================== Low Level Design (LLD) is crucial for building software that is modular, flexible, maintainable, and reusable. In interviews, you'll be assessed on your ability to apply Object-Oriented Design (OOD) principles and design patterns to solve real-world problems effectively. Here’s how to ace LLD interviews and demonstrate your design skills: What is LLD? ========= LLD is the component-level design process that involves refining system details step by step. It focuses on: -> Class diagrams with methods and class relationships -> Program specifications and low-level system details LLD is also known as Object-Oriented Design (OOD) and is fundamental for designing clean, efficient software systems. What Interviewers Expect? =================== In LLD interviews, you'll be judged based on your ability to create modular, flexible, and maintainable systems using OOD principles and design patterns. How to Prepare: -> Master an OOP Language: Familiarize yourself with one Object-Oriented Programming language (e.g., Java, C++, Python). -> Understand SOLID Principles: Study SOLID principles for clean, maintainable code. -> Learn Design Patterns: Gain a strong grasp of commonly used design patterns (Singleton, Factory, Strategy, etc.). -> Solve Common LLD Problems: Practice solving real-world problems to build your LLD skills. How to Tackle LLD Problems in Interviews =============================== -> Clarify the Problem: Ask relevant questions to ensure you fully understand the requirements before diving into the design. -> Identify Core Classes/Objects: Break down the system into its main components (classes and objects). -> Establish Relationships: Observe how different objects interact and define their relationships. -> Define Methods: Identify and define the methods needed for each class to fulfil the requirements. -> Apply OOD Principles & Patterns: Use appropriate design patterns and principles to enhance the system’s readability, maintainability, and reusability. -> Write Clean Code: If tasked with implementation, ensure your code is well-structured and adheres to clean coding practices.
-
A few years ago, we spotted something interesting on monday.com’s signup page: A blurred version of their dashboard is in the background of the signup form. It looked great - but did it work? We A/B tested it in a couple of our products. No major impact (for us - though it might work differently for you). Fast forward to a few months ago: We decided to try it again - this time in Poptin’s onboarding flow as part of our new UI. 👉 We added a blurred version of the user dashboard behind each onboarding screen. 👉 We removed one of the questions (Less friction, even though it was pre-filled) 👉 We removed the progress bar (We might a/b test more versions with it later) 👉 We tracked the entire flow in the database, both before and after the change. 👉 We tested each version with approximately 10K signups. The result? It blew us away: 🔵 Before (solid color background): 35% of signups completed onboarding 🟢 After (blurred dashboard background): 51% completed onboarding That’s a 45% increase 🚀 💡 Pro tip: As users progress through onboarding, gradually reduce the blur or dark overlay to signal they’re getting closer to the finish line. (For context: popup creation rate and plan purchase rate stayed about the same - but total numbers were significantly higher due to better onboarding completion.) Sometimes, the smallest UX tweaks make the biggest difference. ____________ Gal and I started to post SaaS growth hacks & strategies on a weekly basis. You'll be able to check them out by clicking on #TomerAndGal #ux #onboarding #cro #poptin #signup
-
🧪 Atomic Design: Building UI Systems That Scale Designing great interfaces isn’t just about making screens look good — it’s about building a system that stays consistent, scalable, and easy to maintain as your product grows. That’s where Atomic Design by Brad Frost comes in — a brilliant methodology that helps UX/UI specialists create robust, modular design systems — not just isolated pages. Here’s how it breaks down: 🔹 Atoms – The smallest building blocks of UI: buttons, inputs, labels. 🔹 Molecules – Groups of atoms forming small functional components (e.g., a search bar with label + input + button). 🔹 Organisms – Larger interface sections made of molecules & atoms, like headers or cards. 🔹 Templates – Page-level layouts that arrange organisms & define content hierarchy. 🔹 Pages – Fully realized screens with real content where the user experience is validated. ✨ Why it matters: Atomic Design gives teams a shared design language, ensures consistency across screens, and allows for scalable growth — so you spend less time fixing inconsistencies and more time improving the user experience. 💬 Whether you're designing a startup MVP or a global product, thinking in systems (not screens) is the fastest way to build cohesive, future-proof designs. ❤️ Save this post for your next design sprint. 🔁 Share with your design team and start speaking the same visual language today. #UXDesign #UIDesign #AtomicDesign #DesignSystems #ComponentDesign #ScalableUI #ProductDesign #UXStrategy #AtomicDesignMethodology #DesignThinking
-
Best Practices for Writing Clean and Maintainable Code One of the worst headaches is trying to understand and work with poorly written code, especially when the logic isn’t clear. Writing clean, maintainable, and testable code—and adhering to design patterns and principles—is a must in today’s fast-paced development environment. Here are a few strategies to help you achieve this: 1. Choose Meaningful Names: Opt for descriptive names for your variables, functions, and classes to make your code more intuitive and accessible. 2. Maintain Consistent Naming Conventions: Stick to a uniform naming style (camelCase, snake_case, etc.) across your project for consistency and clarity. 3. Embrace Modularity: Break down complex tasks into smaller, reusable modules or functions. This makes both debugging and testing more manageable. 4. Comment and Document Wisely: Even if your code is clear, thoughtful comments and documentation can provide helpful context, especially for new team members. 5. Simplicity Over Complexity: Keep your code straightforward to enhance readability and reduce the likelihood of bugs. 6. Leverage Version Control: Utilize tools like Git to manage changes, collaborate seamlessly, and maintain a history of your code. 7. Refactor Regularly: Continuously review and refine your code to remove redundancies and improve structure without altering functionality. 8. Follow SOLID Principles & Design Patterns: Applying SOLID principles and well-established design patterns ensures your code is scalable, adaptable, and easy to extend over time. 9. Test Your Code: Write unit and integration tests to ensure reliability and make future maintenance easier. Incorporating these tips into your development routine will lead to code that’s easier to understand, collaborate on, and improve. #CleanCode #SoftwareEngineering #CodingBestPractices #CodeQuality #DevTips
Explore categories
- Hospitality & Tourism
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Healthcare
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Career
- Business Strategy
- Change Management
- Organizational Culture
- Innovation
- Event Planning
- Training & Development