“Software is a great combination between artistry and engineering.” — Bill Gates
Top Down and Bottom Up
Phrases like “top-down” or “bottom-up” can mean a lot. Maybe you’re thinking about how projects are divided into milestones before milestones are defined as sets of tasks, how your brain processes information, or defining a hierarchical organization in a company.
I’ve never been able to fully wrap my head around the idea of software being built either up or down.
Some programmers use these terms to describe the processes for designing and building software. Building bottom-up consists of developing building blocks that stack and rearrange to shape out the rest of the system.
I’ve never been able to fully wrap my head around the idea of software being built either up or down. Maybe you can think about building a program as constructing a building where the foundation is laid, then a frame or structure goes up, etc. But software doesn’t really play by the same rules. Going along with the building analogy, when writing software I might build a bridge between two buildings before those two buildings exist. It’s hard to conceptualize a floating bridge connecting two non-existent buildings. A lot of things can take your program down but gravity is pretty low on the list.
How About Free Floating Components?
I end up thinking about software as these components floating in free space that can handle IO and contain multiple sub-components within them.
Thinking about it that way, it’s more about designing software “inside out” or “outside in”.

After going through school and working in software, I haven’t heard these terms thrown around too much. Inside out seems to be assumed across the board. You hear a lot about writing testable code, reusable functions, single responsibility components, writing tests before the code (TDD/BDD), breaking large problems into small pieces (Algorithmic Thinking), etc.
Those are some great ways to approach code especially if you have plenty of time to do all of the planning, pseudocode, and pen and paper system designing that you need to do. But realistically, you just run into situations where you need to be more efficient than that. And ideally not write horrible code in the process that is just going to be refactored later.
Defining Outside In
Try scrapping the idea of writing units first. No tests first, no functional methods, no inner workings of your program. It’s so easy to focus on the hardest problem first which means you jump right into trying to implement an algorithm or define complicated logic.
Instead, we will contradict that idea and do the following:
- Define the entry point for the code.
- Assume that any module, function, helper, library, etc. is available, even if the code does not exist.
- Write the ideal version of the entry point.
- Write the ideal version of any code that was assumed until you start reaching “low level” functions that perform the bulk of the logic.
- Repeat until you have all functions defined.
- Now actually implement the logic for the “low level” functions.
Coding is a combination of creativity and common engineering practices. There are thousands of ways to approach a problem and still come up with the same answer. Outside in development is a way to think about software development from a high level so that we can work through problems efficiently on the fly while still producing good, readable, testable code.
An Example
We are going to look at building the start of a forecasting system that takes a set of project management tasks and builds out a schedule for the assigned users.
- This is on a web application where users will log in to see their tasks and schedule.
- It will be connected to a persistent database.
- Assume every employee works 8 hours a day M-F
- Assume every employee takes off federal holidays in the US
- Add 2 days of padding for each timeline entry
- Use a task data structure that consists of a name, deadline, assigned user, project reference, and estimate hours.
The Entry Point
With working on units first we might have started with building out a class and a few functions like this. In this case, we would be jumping into the nitty gritty of the scheduling functions.
# time.py
class DT(object):
def get_us_holidays(self):
pass
def get_weekdays(self):
pass
def set_back_days(self, days):
pass
def set_back_weekdays(self, weekdays):
pass
Instead, let’s try finding our entry point for the system and working outside in. With this being a web application, we know users are going to going to load a certain view to see their schedule. Let’s say they are going to be doing that through some sort of controller:
class ScheduleController(Controller):
@get('/my/schedule')
def show_schedule(self):
"""
Shows a set of forecast items for all of the tasks the
current user has defined.
"""
In this case, maybe we actually have a framework behind the scenes where we can access Controller
and @get
or maybe we don’t. Either way, we want to write this in the simplest, most ideal scenario
possible even if the code doesn’t exist. We’ll deal with the details later.
Now we move on to writing the ideal version of show_schedule
.
from app import http, auth
@http.auth()
@get('/my/schedule')
def show_schedule(self):
"""
Shows a set of forecast items for all of the tasks the
current user has defined.
Assume that the schedule has already been generated by
the task object throughout their lifecycle.
"""
current_user = auth.current_user()
return http.response(
view='my.schedule',
data={'tasks': current_user.tasks}
)
We are making even more assumptions here:
- Assume that we have
http
andauth
available. - Assume that
http.auth
is an annotation available. - Assume that
http.response
is a function available. - Assume that
auth.current_user
is a function available. - Assume that the
response
function handles view based on a string and accepts a dictionary of data. - Assume that the user getting returned from
current_user
has a property calledtasks
available.
These are all details that we could have dove into immediately or even start coding within our show_schedule
function just to get them off of our mind. But if you can make the assumptions and not worry about the details
until later then you’re not prohibited and can just focus on writing the elegant solution.
Digging Deeper
Once the entry point is coded out then you just start funneling deeper into the program (i.e. outside in). Let’s start accounting for functions we know do not exist.
# app/http.py
from app import views
def response(view, data=None):
"""
Send an http response back to the client.
:param view: str
:param data: None|dict
"""
return views.render(name=view, data=data)
def auth(fn):
"""
@http.auth() annotation to prevent users
from access a certain view in our app.
"""
def annotation():
pass
return annotation
# app/views.py
def render(name=None, data=None, content=None):
if not name and not content:
raise Exception('Pass either a file path through 'name' or file contents through 'content')
if name:
# Process a view from a file path
pass
elif content:
# Process a view from the file contents
pass
You can see how we just keep recursing down further into the program.
- Create the
app/http.py
module and make sure we have the functions that we need available forshow_schedule
. - Write them in an ideal way.
- Since we need a way to render views, create our
app/views.py
and add therender
function. This is the point where we are getting pretty low level and the bulk of the view rendering logic would probably live. That’s up to you to decide as the programmer of course.
We could do it again with the auth.py
helper:
# app/auth.py
from app import session
def current_user():
if not session.active():
raise Exception('There is no active session.')
return session.active().user
# app/session.py
import psycopg2
def active():
"""
Check if there is an active session.
"""
return _get_current_session()
def _get_current_session():
return psycopg2.execute('SELECT * FROM sessions WHERE active=true;')
- Create the
app/auth.py
module and make sure we have the functions that we need available forshow_schedule
. - Write them in an ideal way.
- Since we need a way to render views, create our
app/session.py
and add theactive
function. This is another point where a lot of our “low level” logic could live. Similar to theviews.py
we created.
Conclusion
I’m not going to write out the entire program here since it could get pretty lengthy if we assume there is no framework but hopefully, that gives you an idea on how to approach code outside in.
It’s a different way to think about things and not perfect for every scenario, but a great tool to add to your skill set.
Thanks For Reading
I appreciate you taking the time to read any of my articles. I hope it has helped you out in some way. If you're looking for more ramblings, take a look at theentire catalog of articles I've written. Give me a follow on Twitter or Github to see what else I've got going on. Feel free to reach out if you want to talk!