How to Save Time at a Hackathon
11 Tips from Startup-land
Hack Princeton 2015 is coming up this weekend, and Body Labs, the company where I work, is a sponsor. When our director of product Jonathan Schwartz asked whether I had advice for hackathon participants, here’s what I had to say.
In a startup, the too-small engineering team sprints to create a product and deliver it to market during a golden window: after it’s obviously needed, and before the competition arrives. As a startup’s next successful funding round is the only thing that matters, the focus is on driving metrics that can be touted to investors. So, a lean startup produces features and creates infrastructure in ways that are less than optimal — deliberately. This approach leads to a double win: by shipping their product months or weeks sooner the team gets more time to show traction, and they can find out how real customers want to use the product.
This last point — that early shipping provides early data about what is important — is the essence of YAGNI, or You Ain’t Gonna Need It. If doing something well takes your team four months, and doing it roughly takes one month, then maybe do it roughly now, and then in a few months see if it still seems important.
There’s a feedback loop here: roughly identify what needs to be built, think about how much effort it will take to do it well, and then ask, how can it be done with much less effort?
When you compare the rough version to the correct version, usually the differences will seem very important. YAGNI’s reminder is the rough version is probably good enough. In a few months, that powerful detail may not seem as important as it does right now. There’s another benefit to deferring the complex version. Equipped with a deep understanding of the simple version —both from implementing it, and months of use — you can apply the feedback loop again, and might be surprised to discover a more minimal way to build the added functionality.
Congruently, a hackathon team races to an imminent deadline, aiming to deliver a successful demo. The demo is all that matters. Effort is measured in hours instead of months, but there is far too little time to do everything well and get it integrated before demo hour. Furthermore — and this may be surprising — you don’t know for sure what will be important in your demo.
I learned this from experience: one of my most successful hackathon demos was a web application with no integrated back end. It was most apparent in our “shopping cart” page, which showed the same three items regardless of what the user has added. A clever rehearsed demo covered this up entirely, but if we’d thought to “defer” the entire back end, we could have gotten more features built… and gotten a little more sleep that night. To the average person watching our demo — and even the average judge — the “rough” and “perfect” implementations were identical.
Sometimes a seemingly insane choice — like no back end — yields the largest reduction in effort.
So, at a hackathon, can you allocate your resources like a lean startup? Tick off each feature in a really rough way, even if the feature or approach seems inferior. This is especially true when the rough approach seems insane. That’s because, sometimes, it’s a seemingly insane choice — like no back end — which yields the largest reduction in effort.
Without further ado, here are 11 practical tips for applying YAGNI to save time at a hackathon.
1. Build a demo, not an app
Build a minimum demo. You don’t need to show the entire app or product, just enough to convey the concept. Focus on features that show well, and start by implementing them roughly and as quickly as possible, then iterate.
For example, if your app includes a map with user-contributed content, a rough version might have a static map, with interactive clickable / hoverable areas added manually in code. Later, if there’s time, you can add a “data layer” where this content can be specified and persisted dynamically.
Put another way: build the facade first, not the engine.
2. Skip Git, use Dropbox
Use Dropbox. Since it’s easier to set up than Bitbucket or Github, it lets you hit the ground running. Dropbox supports a more rapid pace of development, and these kind of minute-on-minute savings add up in a hackathon. It won’t manage conflicts for you the way Git will. Instead you can prevent conflicts — simply by talking to your teammates.
If you’re the sort who likes to have backups (like I am), then periodically copy the entire project to some location outside of Dropbox. The best time to do this is when the project is in a relatively stable state, like when you have just finished a feature. Name these backups with the date and time. You can then restore individual files, and if later something breaks entirely, you have the option of referring back to these old copies as you sort out what has changed. You can also use Dropbox’s history, but I find these project-level snapshots to be more helpful than the file-level snapshots Dropbox provides.
3. You ain’t gonna need real persistence
Typically the back end of an app is a database-backed web service. If you build the facade first, you may not immediately need a back end. In fact, as I allude to above, a successful hackathon project might not need a fully functional persistence layer. There’s a progression of dead-simple and lightweight persistence options to consider, which I’ll describe in detail here.
- Browser state. In a single-page app, most or all of the app’s lifecycle occurs within a single page load. In this context, persistence is simple: your model objects can simply live in the browser’s memory, for the duration of the app. Simply collect the persistent state in a global object called database, stores, or something similar. You need not use an actual global variable for this: if you’re using dependency injection in Angular or passing props in React, you can hold the persistent store at the top level and inject your content down your app’s hierarchy.
- Browser state + static content or fixtures. In most cases, you’ll need to provide some sample content to show in your app. A to-do app needs lists of tasks; a marketplace app needs some products and sellers. You can handle this by creating fixtures, which basically are static JSON files with your initial data. When your app starts up, load these JSON files from the server and deserialize them into your stores. If you prefer, as an alternative to a JSON file, you can also create the static content directly in your JavaScript code. Later, if/when you have time to build a real persistence layer, you can easily swap in some server-loading code for the fixture-loading code, and migrate the sample data into the persistent store.
- Local Storage. You can go pretty far by specifying static content in your fixtures and scripts, but at some point keeping fixtures updated may become too tedious, and you’ll really need to persist state across reloads of the page. Turn to Local Storage. Migrate your user-specific data there, while keeping shared data in static JSON files. A couple tips on using Local Storage: first, it can only hold strings, so you’ll need to serialize and JSON.stringify your objects. Second, though it’s easy to clear your data (use the developer console), it’s a bit tricky to manipulate it. Probably you will need to clear it out pretty often during development. So, make sure your app can automatically re-initialize itself from a blank slate, using sensible default from code or fixtures — and that you’ve saved a copy elsewhere of any important data.
- Lightweight HTTP backend. You can go pretty far using Local Storage (Chrome allows you to store about 10 MB), but at some point you may need your app to keep its state across multiple computers. At this point, consider using a very simple backend: something like Firebase, CouchDB, or Parse Core. Each of these tools provide schemaless persistence that you can consume directly from your app, without needing to build controllers or a database layer on the server side. These tools even support authentication and TLS encryption, so they can back your apps in production.
Obviously some apps are different. If you’re building an image-matching system, you probably can’t run your matching algorithm in the browser. If you’re building a chat system, you’ll probably need to develop with two copies of the app that talk to each other. But the principles of YAGNI still apply. Make your backend as simple as possible. Mix and match: store your chat history in memory or in Local Storage, and use the server just to exchange messages. (And don’t forget you can use Local Storage to pass messages between instances of your app! If your app is running in two different tabs in Chrome, this works like a charm.)
4. Integrate early and often
Integration is the process of taking two pieces of working code and getting them to work with each other. If you’re designing with components and splitting up work with your team members, as you should be, integration is the pivotal moment when you bring your work back together.
Expecting unicorns and rainbows? Think again. Integration is rubber meeting the road. Or puking rainbows.
During integration you will uncover divergent assumptions and code which is not as robust as you believed. You’ll notice flouted agreements: nested vs. flat, one vs. many, ordered list vs. unordered dictionary, numeric string vs. number, lastname vs. last_name vs. lastName, /todo vs /todos vs /todos/. You’ll discover certain complications: Accept headers, CORS, and your framework’s CSRF protection.
Resolving each incompatibility requires some patience and a flexible mind. Sometimes you can sync up your naming conventions with trivial changes. Or track down some documentation, and then reconfigure your server or set some HTTP header in your client, a minor chore. Sometimes though, the differences throw light on a fundamental design error. Depending how each piece of code is written, fixing it might be fast and easy. Or it might be hard. And it may be difficult for you and your team members to agree on how to resolve the conflict.
I’ve experienced teams atrophy at this stage. So many teams — at hackathons, at work, and in undergrad software-engineering courses. It’s made me a pessimist about unintegrated code. Until you’ve experienced the frustration of integration, it’s difficult to convey just how much time, effort, and patience this can take.
What’s the alternative? Keep your non-integrated code to a minimum. The less code you are integrating at a time, the easier it is. When you haven’t invested time writing big chunks of code, you’ll be less frustrated to make adjustments and compromises. If you start your persistence layer at the light end, refactoring up when needed, this is pretty easy.
Team dynamics aside: code that’s not integrated can’t be shown in a demo. Also, when you’re working on a tight timeline, an integrated project is easier to apprehend. When you can see the big picture — the 10,000-foot perspective of your audience and the judges — you can make better decisions about how you spend the precious little time you have.
If your project were a business, your unintegrated code would be your inventory. Minimize your inventory, and you minimize your risk, to the demo and the team.
5. You ain’t gonna need accounts
A real app will need to support multiple users across a single deployment. But your app might not need to, at least not right away. Signup and login flow can be tricky to get working, so leaving them out can save you a good bit of time.
Also, even if your app does need to support multiple users, make your life much simpler by using the honor system. First, don’t bother to use passwords, or any kind of authentication. Identify an account in the simplest way that can possibly work, like typing in the user’s first name. Second, don’t bother to keep one user’s data private from another’s. Just trust your front end to access the data for the desired user. Your document can use a namespace for each user.
{
users: {
Marge: { todos: [...] },
Homer: { todos: [...] },
Lisa: { todos: [...] }
}
}
6. You ain’t gonna need cross-browser
Pick a browser to work in. If you don’t know what to pick, just use Chrome. Even if you ignore important browser compatibility issues, you can always patch them later.
If your app is responsive, you can just resize the window to demonstrate the mobile experience. Your app does not need to support Mobile Safari this weekend. Your app doesn’t need to support any version of IE this weekend.
7. Explore a JavaScript framework
In facade-driven development, the front end is really important, and a framework is going to come very much in handy. If you’re going to invest in learning one new thing, a JS framework may be a good choice. With a framework in your toolbelt, you’ll be more prepared for your next project.
There are many good choices of JavaScript frameworks: React, Backbone, Angular, Ember. (At Body Labs we’re writing new code in React and have older bits in Angular.) Choose one of these frameworks quickly. Get something on the screen as soon as possible. Then, make it better.
A word about jQuery: jQuery has been the most popular JavaScript library for a long time. It’s great at DOM manipulation, having pioneered the idea of finding elements using CSS selectors and manipulating them en masse. It used to be important for achieving compatibility across browsers, but today (and certainly this weekend — see #6) it’s largely unnecessary for that. The greatest value in jQuery comes from its immense popularity over the last nine years, which has led to high “Googleability”: you’ll find snippets all over the web. Despite being a good tool for many things, it’s not the best tool for anything. I don’t recommend learning it if you don’t already know it.
If you use jQuery and you’re ready to start weaning yourself off, try replacing $.get with a fetch polyfill. Or check out You Might Not Need jQuery for more advice.
8. Use Bower
Use Bower. It’s the easiest way to pull in front-end components: things like Underscore, Twitter Bootstrap, Bourbon, Angular, React, and Three.js. Bower isn’t perfect, but it’s good for a hackathon. It’s simple to use, and plays well with Dropbox. Simply create bower.json in your project folder inside Dropbox, install Bower on at least one dev machine, and bower_components will sync to your other machines. This should get your team up to speed quickly and make it easier to collaborate tightly.
When it’s time to pull in new dependencies, you might want to install Bower on additional machines. Only one person should run it a time, so coordinate with each other, and wait for it to sync.
The npm package manager is a good alternative, especially with Browserify, and worth learning. But for a hackathon, especially with a heterogeneous team, it’s not worth the overhead unless you’ve used it before. It’ll probably slow you down more than it’ll speed you up.
Here’s the single most important tip for Bower: never edit in your bower_components folder, as Bower will blow away your changes with impunity.
9. In development, use a zero-configuration HTTP server, and add build tools if you need them
Install nvm, node, and npm install -g http-server. Serve up your static files that way. http-server is extraordinarily simple to use. Installing npm is a bit of extra work, but it also puts many handy tools at your fingertips.
Working from file URLs seems like an alluring shortcut, but is more trouble than it’s worth. You can’t make Ajax calls to load JSON content over file URLs. You may run into weird CORS issues. And finally, you can run into new issues when you do end up moving your code to a server.
As in #3 above, remember you can mix-and-match. If you build a back end, it can serve up your API calls while you keep your front-end pages under http-server. There are three advantages: first, you don’t have to configure your server framework for static files; second, you don’t need to synchronize server-configuration changes between developers; and third, if you do need to deploy your app, you can easily deploy the front end and back end separately.
It’s a good idea to keep your tooling light, but three tools you might want to investigate are JSHint, Sass, and Assemble.
First, JSHint, because static analysis is for people who care. To make sure you’re using the good parts of JavaScript (read Douglas Crockford’s book!), you may want to run JSHint to find the more egregious problems in your code. It’s easy to install and run from npm. One or two of your team can set this up and run it periodically. You don’t necessarily need to get it running on every developer machine.
If you need to use something like Sass, which is a great CSS preprocessor, you have a couple options. If one person will edit most of the CSS, run the watch process on their machine, and store both the .sass and generated .css in Dropbox. If another developer changes a sass file, eventually it’ll sync to the main machine with watch running, and the compiler will pick it up. If you have many people working together on the front end, you probably need to run watch on each machine. In that case, keep the generated files outside of Dropbox (so they can be different on each machine). The downside is that every machine then needs to run sass in order to view the front end.
The easiest way to rapidly deal with templated content is to use something like Angular which loads templates in the browser. But another good option for dealing with dynamic content is to assemble the page on the dev machine. (In a deployment environment, you’d push the generated static content directly to the server.) There are lots of good tools for this, but one I recommend is Assemble. It’s built on top of Handlebars and Grunt, both of which are pretty easy to pick up.
Don’t worry about minifying. You probably don’t need to worry about concatenation either.
10. You ain’t gonna need a deployment environment
If you’ve followed this advice so far, you’ve probably realized that it emphasizes the development environment. For most hackathon projects this is completely adequate.
This is a good thing: deployment is a kind of integration, and it’s always trickier than it seems. You’ll uncover undeclared dependencies, hard-coded URIs, SSL problems, timing- and cache-related issues, dependency version skew, local path assumptions on the server, and virtual resource limits like out-of-memory errors. In a typical project these are important to get right as soon as possible and I’d advise dev–prod parity and early deployment. But, at a hackathon, it’s an entire class of bugs you can defer.
If you do need to deploy your app to run on a public web server, consider using newcomer Surge or S3 for static files and Heroku for your API. Heroku has recently added support for Dropbox: check it out. (Though you may want to use Git if you’re using Heroku.)
11. In your demo, tell a story
At some point, one or two of your team should step away from the text editor and debugger console and think about your app from a high level. Plan your presentation. There are a few parts that are important to cover:
- An introduction
- A demo which flows through your app (allot half of your time here)
- Thoughts on how you imagine it evolving
- Some information about how you’ve built it
- Introduce your team
The usual tips about presentations apply: Get your audience to laugh. Tell a story. Show off your stuff. Don’t complain. Don’t apologize for what didn’t come out the way you wanted.
I can’t emphasize this enough: rehearse your interactions with the actual app. If possible, run your demo locally on your laptop, maybe with wifi turned off. It’s amazing how frequently wifi problems pop up during demos.
Sleep before your demo: it’s helpful to have your brain on your side.
Finally, and this is important: be in it for the long haul. When I said earlier, “a successful demo is all that matters,” I meant it — but it’s not the whole story. There are two even more important things to get out of your weekend: have fun, and improve your understanding of how the pieces fit together. Consider how much better you will understand what you are doing the next time around, and the more impactful contributions you’ll make. That long-haul growth is what will make you a stronger software developer.
So in summary: start simple and iterate, learn what you can, and have fun. Best of luck!
Paul Melnikow is employee #5 at Body Labs, an early-stage startup which makes virtual 3D models of human bodies and tools for building applications surrounding the body. There — and elsewhere — he writes JavaScript, Python, Objective-C, Ruby, Swift, and C#. Paul was lead developer on Body Snap, a scanning app for Kinect for Windows (tech talk, NY Tech Meetup demo). The team from Body Labs took second place at the Kinect Hackathon in New York City, with ScanAdHoc, a multi-Kinect body scanning app. His favorite hackathon project was FreshQuest, a mobile website to help people find local products at a large farmer’s market. It won second place at the inaugural Hack Upstate, in Syracuse, N.Y. Paul and Jonathan will represent Body Labs at Hack Princeton, April 11–12, 2015. Thanks to Julia Gilroy for proofreading this post.