Guiding Principles for Imgur Engineers

Muts Inayama
Imgur Engineering
Published in
9 min readFeb 12, 2018

Back in April 2017, our CEO/CTO Alan Schaaf, our Engineering Leadership team, and I set out to re-establish and document what we felt was important as engineers at Imgur. We noticed that some software that we built successfully surpassed the test of time while other solutions somehow missed the mark because they were brittle, buggy, or simply failed to address business needs. We distilled our observations of how to build better software into a draft and circulated our thoughts across our engineering team for feedback. This quickly became an all team effort as we incorporated tons of input from almost everyone across the org. We wanted principles that were applicable to all of our teams, so one of the challenges was that they needed to be high-level but still relevant. Nothing we list are novel — I believe they serve more as good reminders rather than new concepts. After several revisions with the team, we solidified what we think of as our guiding principles as Imgur engineers.

Every week, during our Engineering Leadership meeting, one of the leaders will read an excerpt from the guide and discuss an engineer that lived out one of our principles. For instance, this past week, Aaron Wynn mentioned that Bjorn Tipling exemplified “iron sharpens iron” by reviewing the architecture and code behind the Vertical Feed project.

Engineering at Imgur

The purpose of this document is to help you develop a mental framework for how we perform software engineering at Imgur, identify what we believe is core to software engineering, and to ultimately avoid common pitfalls of software engineering and bad software design. It will also allow us to all be on the same page about how we build things, and ease tensions between teams. Following and being aligned with this document will allow Imgur to achieve its vision of lifting the world’s spirits for a few moments everyday, and continue to be a successful organization.

Planning is integral to good software and process

We aim to have a systematic process for creatively solving problems.

  1. Understand the problem
  2. Plan a solution
  3. Carry out that plan
  4. Examine your results for accuracy

Most of the significant effort goes into the first two parts of the process. If you fail either of those, it hardly matters whether you’ve carried out your plan perfectly or accurately. If you want to be a successful engineer at Imgur, understand your problems fully BEFORE diving into the implementation of your solution.

This is easier said than done — engineers often love diving right into building something interesting instead of making sure they’re solving the right problem. A similar principle applies to engineers writing code — if you find yourself coding mostly because building is fun and you didn’t fully explore the problem or plan your approach ahead of time, then you will waste a lot of time.

Imgur Engineering Beliefs

  1. Put users first: We believe that our users and community always come first. Never hold back on doing what’s right for the user because it’s easier for yourself. Don’t assume that your user has the same level of technical competency or product familiarity as you do.
  2. Add value: Our job is to improve Imgur. We want our Imgurians to be happy and make Imgur an awesome destination. Technology is merely a means by which we make that happen. Our mission is to add value, by implementing features and services to make our Imgurians’ experience as awesome as our content!
  3. Act as a leader: Think about how you can enable the team to move fast collectively, before thinking about how you can get the task quickly for yourself. We call this optimizing for the global maximum instead of the local maximum. (Optimize for the bigger picture, not the specific problem you’re solving). Going off on your own and doing your own thing slows the team down in the long run, no matter how fast you’re personally moving at the time. (#behumble)
  4. Assume positive intent: It’s very easy to misunderstand each other, especially over Slack. Whatever is said or done, assume positive intent. When you look for positive intent, you give people the benefit of the doubt and you give yourself the chance to learn the details of the situation. Similarly, you may encounter implementations or decisions that you do not like or simply understand. Rather than assuming that something is wrong or done poorly, perhaps there are other circumstances that you are unaware of that led to the solution.
  5. Iron sharpens iron: We elevate ourselves as a team by sharpening each other’s craft and professionalism as engineers. No one else is going to push us to become better engineers except ourselves. We are committed to helping each other grow by giving honest and constructive feedback to each other (e.g. through code reviews and 360 feedbacks). We will always sharpen each other in a supportive and nurturing manner (assume positive intent.) We will also sharpen each other’s critical thinking by debating passionately over technical choices until we reach a decision that we stick with as a team.

When starting a project

  1. THINK: Think about the project holistically before you try to implement a solution, and have a clear vision for the project. Simply the most important thing you can possibly do. Seriously. You can’t just rush into a solution with guns blazing. We’re solving some pretty complex problems, so you need to be mindful of taking a logical and thoughtful approach to solving them and a rigorous approach to managing your projects or they’ll quickly get away from you.
  2. INPUT: You may own the project, but make sure you also include input from others. Have you talked to the consumers of your API endpoint? Have you consulted DB experts on your query’s performance? Have you solicited feedback on the design you just came up with?
  3. KISS (Keep It Simple, Stupid!): We acknowledge our tendencies to build overly complex systems at times, but we admit that simplicity makes our solution so much better. Don’t overthink logical problems and come up with strange and novel solutions when the simple way is simply better.
  4. UNDERSTAND the impact to the business: It’s always best to understand the impact your project has to the business. What metrics is it trying to move? What will it do for the company in the long run? The answers to those questions may end up changing the way you code or architect, or how you problem solve. Make sure what you’re doing make lines up with what the company is trying to achieve. If you don’t understand the impact to the business, just ask!

Design Traits to Avoid:

A piece of software that fulfills its requirement and yet exhibits any or all of the following traits can be considered to have “bad design”:

  • Fragility: When you make a change, unexpected parts of the system break.
  • Immobility: It’s hard to reuse a chunk of code elsewhere because it cannot be disentangled from its current application/usage.
  • Viscosity: It’s hard to do the “right thing” so developers take alternate actions.

These symptoms nicely encapsulate things you really want to avoid, and most of the principles that people throw around, and that are outlined in this document, are just some of the guidelines to avoid going to the house of bad design.

When writing code

  1. Consistency: It is easier to do things in a familiar context. This serves two purposes: first, it makes reading the code easier; second, it allows programmers to automate skills required in coding. This frees the mind to deal with more important issues. We aim to have as much consistency in the tools that we use as possible.
  2. Function before fashion: First, make it WORK. Then, make it work RIGHT. Finally, make it pretty. Even if it’s held together with string and duct tape, see if it’s worth investing the time to make it work right. In the final phase of the process should you actually optimize your solution to “look” good (e.g. refactoring your code).
  3. YAGNI (You Ain’t Gonna Need It!): Too many times, there is a strong temptation to build code that could respond to every future eventuality by being incredibly flexible and “perfect”. Don’t do it! You’re wasting effort because you really aren’t going to need all those extra features or options or flexibilities. Just build what you need. Trust us and every other engineer who’s looked over old code and facepalmed at all the wasted effort anticipating situations that never materialized.
  4. DRY (Don’t Repeat Yourself): One of the best things about code is how reusable it is. If you write a cool bit of code that solves a useful problem in one place, refer back to it when the problem comes up in other places as well. From your perspective, any time you find yourself manually typing something in multiple times, there’s a way to combine it all into a single task that gets run multiple times.
  5. Don’t Reinvent The Wheel: Any time you’re building code to do something general that’s not directly related to the fundamentals of your application, someone else probably already wrote that code and better. It’s either posted on a blog somewhere, on Stack Overflow, or open-sourced as a module. Learn from and use their code instead of wasting your time reinventing the wheel. We are “lean startup” and have limited “innovation tokens” we can spend. We must invest them wisely.
  6. Kaizen (leave it better than when you found it): Fix not just the bug you’re trying to solve but the code around it. A band-aid bug-fix doesn’t help if the real problem is a design flaw (which it usually is).
  7. Separation of Concerns: Separation of concerns is a recognition of the need for human beings to work within a limited context. Concerns are the different aspects of software functionality. For instance, the “business logic” of software is a concern, and the interface through which a person uses this logic is another. The separation of concerns is keeping the code for each of these concerns separate. To take this further, we separate entire systems. Microservices and separate repositories are examples of this. Separating systems into their own isolated environments helps prevent one system from affecting another, and eases monitoring, deployment, and development.
  8. Write code to be read: Even if you don’t intend anybody else to read your code, there’s still a very good chance that somebody will have to stare at your code and figure out what it does: That person is probably going to be you, twelve months from now. Always make sure you’re writing code that makes reading it easier for yourself and others in the future.

As You “Ship Early, Ship Often”

  1. Be proud of your code: Never ship code that you’re not personally proud of. Chances are that if you’re not proud of your code, it’s probably bad code and you shouldn’t ship it.
  2. Be your own QA, and test your code: Never ship code or make pull requests unless you’ve personally tested the heck out of it. It’s not good enough to just refresh the page / build the app and see that the error is gone. You have to actually test it thoroughly yourself.
  3. Ask yourself “How will this break?” not “Will this break?”: Before shipping, if you ask yourself “Will this break?”, you’ve fallen into a mental trap where you will almost inevitably tell yourself “naw, it will be ok”. Instead, try asking yourself “How will this break?”. Framing this question correctly will open your mind to think of all the possible ways your software could go wrong and you’ll end up uncovering things that you otherwise would have missed.
  4. It will fail one day: We build our systems with the expectation that one day it will eventually fail. When it does, it should never impact more than itself, and we should know immediately when it happens. We should also have the proper backups and procedures in place to get the failed system back online.
  5. Incremental Development & Continuous Deployment: Build software in small increments; for example, adding one use case at a time. If you develop software by adding small increments of functionality then, for verification, you only need to deal with the added portion. A carefully planned incremental development process can also ease the handling of changes in requirements. To do this, the planning must identify use cases that are most likely to be changed and put them towards the end of the development process. Incremental development and continuous deployment is a means of how we Ship Early and Ship Often. It aims at building, testing, and releasing software faster and more frequently.
  6. Be like a router (have outstanding communication): A router takes information and sends it to the right person. When making a decision, always ask yourself “who else should know about this?”. Do this with emails, slack messages, and everything else that comes to you. The only way you can get into trouble while making decisions at Imgur is if you don’t end up telling the right people. Remember to be a router and let others know about the decisions you’re making!

--

--