Starting up...

Using function.length to implement a stack language

/* Functions in JavaScript have a length property, measuring the number of named formal parameters. This means that a stack-based language can tell how many items to pop off the stack to pass to the function.

Here is a very simple stack language, implemented in quasi-literate JavaScript.

(That is, you can copy this post out of your browser into a .js file and run it in Node.) */

var interpreter = {

/* The “Standard Library” is implemented as a dictionary of plain JavaScript functions. */

    '+': function(a, b) { return [a + b]; },
    '-': function(a, b) { return [a - b]; },
    'dup': function(a) { return [a, a]; },
    'swap': function(a, b) { return [b, a]; },
    /* Must be wrapped. `console.log.length` === 0 */
    '.': function(i) { console.log(i); },

/* run is the entry point to the interpreter. It resets the inputBuffer and stack fields. */

    'run': function(program) {
        this.inputBuffer = program;
        this.stack = [];

        while (this.inputBuffer) {
            // read one word from inputBuffer & interpret it
            this.interpret(this.word());
        }
    },

/* word removes one word from the inputBuffer and returns it. */

    'word': function(/* operates on inputBuffer, not stack */) {
        var word = '';
        while (this.inputBuffer) {
            var ch = this.inputBuffer.charAt(0);
            this.inputBuffer = this.inputBuffer.substr(1);
            if (/^\s?$/.test(ch)) {
                if (word) {
                    break;
                }
            } else {
                word += ch;
            }
        }
        return word;
    },

/* interpret decides how to handle each word.

  • If the word is in the dictionary, execute it.
  • If it is a number, push the number onto the stack.
  • Otherwise throw an exception.

*/

    'interpret': function(word) {
        if (!word) return;
        if (word in this) {
            // it's defined, execute it.
            this.execute(this[word]);
        } else if (isFinite(word)) {
            // it's a number
            this.stack.push(parseFloat(word));
        } else throw new Error('unknown word `' + word + '`');
    },

/* execute provides the interoperability with JavaScript Functions. It provides the top fn.length items from the stack as arguments to the function.

If fn returns a value, the value is concatenated back on top of the stack.

Multiple values can be returned in an array; Array.prototype.concat will correctly concatenate all of the values to this.stack in the intended order. */

    'execute': function(fn) {
        var len = fn.length;
        // pop the top `len` items off the stack, in order
        var applies = this.stack.splice(-len, len);
        // apply the items to the Function
        var results = fn.apply(this, applies);
        if (results) {
            // put any results back onto the stack
            this.stack = this.stack.concat(results);
        }
    },
};

/* Unit tests to demonstrate usage */

var assert = require('assert');

/* An empty program leaves an empty stack. */

interpreter.run('');
assert.deepEqual(interpreter.stack, []);

/* The standard library: dup, -, +, swap */

interpreter.run('3 dup');
assert.deepEqual(interpreter.stack, [3, 3]);
interpreter.run("2 6 -");
assert.deepEqual(interpreter.stack, [-4]);
interpreter.run('5 8 +');
assert.deepEqual(interpreter.stack, [13]);
interpreter.run('10 20');
assert.deepEqual(interpreter.stack, [10, 20]);
interpreter.run('10 20 swap');
assert.deepEqual(interpreter.stack, [20, 10]);

/* We can reflect on just about any part of the execution of the interpreter. */

interpreter.run('12 23');
assert.deepEqual(interpreter.stack, [12, 23]);
// continues...

/* For example, calling interpret without run or word can be used to single-step: */

// ...continued
interpreter.interpret('swap');
assert.deepEqual(interpreter.stack, [23, 12]);
// continues...

/* We can execute a function that isn’t even in the dictionary: */

// ...continued
interpreter.execute(function(a, b) {
    assert.deepEqual([a, b], [23, 12]);
    return ['whoa', 'nelly'];
});
assert.deepEqual(interpreter.stack, ['whoa', 'nelly']);

/* Interpreting the word 'execute' pops a function off the stack */

interpreter.stack = [function() { return ['WOW'] }];
interpreter.interpret('execute');
assert.deepEqual(interpreter.stack, ['WOW']);

/* Use the word tokenizer function to put single-word Strings on the stack */

interpreter.run('word foo');
assert.deepEqual(interpreter.stack, ['foo']);

/* The preexisting + operator can work with this new String data type */

interpreter.run('word Hello, word World! +');
assert.deepEqual(interpreter.stack, ['Hello,World!']);

/* And interpret can be called on Strings on the stack */

interpreter.run('5 5 word + interpret');
assert.deepEqual(interpreter.stack, [10]);

/* Even if the String is a result of some other computation! */

interpreter.run('8 9 word sw word ap + interpret');
assert.deepEqual(interpreter.stack, [9, 8]);

Binary Boolean Operator: The Lost Levels

There are sixteen possible binary operations on Boolean inputs. The operations can be numbered based on the truth table used to generate them. For example, the AND function is defined by the following truth table:


P   Q   |  P AND Q
--------+---------
1   1   |     1
1   0   |     0
0   1   |     0
0   0   |     0

By reading down the result column, we determine that AND is function number 1000b, or 8 in decimal.

Here are all 16 of the binary Boolean operations:


#   |  11   10   01   00  |   Common name
----+---------------------+--------------
0   |  0    0    0    0   |   False
1   |  0    0    0    1   |   NOR
2   |  0    0    1    0   |
3   |  0    0    1    1   |   Not P
4   |  0    1    0    0   |
5   |  0    1    0    1   |   Not Q
6   |  0    1    1    0   |   XOR / Not Equal
7   |  0    1    1    1   |   NAND
8   |  1    0    0    0   |   AND
9   |  1    0    0    1   |   Equal
10  |  1    0    1    0   |   Q
11  |  1    0    1    1   |
12  |  1    1    0    0   |   P
13  |  1    1    0    1   |
14  |  1    1    1    0   |   OR
15  |  1    1    1    1   |   True

Almost all of these operators are familiar, but there are four less-familiar operators. 11 and 13 are the same operation, but with their input order reversed. 4 and 2 are the inverses of those two, respectively.

The most widely known of these four siblings is operator number 11. This operator is called the “material conditional”. It is used to test if a statement fits the logical pattern “P implies Q”. It is equivalent to !P || Q by the material implication.

I only know one language that implementes this operation: VBScript.

Instead of writing a test like this:

if needs_val:
    assert(has_val())

or the logically identical, but difficult to read:

assert(not needs_val or has_val()) # BAD! Precedence is difficult to reason about.

you could write it like this:

assert(needs_val Imp has_val()) # AWESOME! (If `Imp` exists!)

Once you get the hang of this operator, you start wishing you could use it everywhere. I wish more languages supported Imp.

Turkey Day Down Time

Based on a true story, Thanksgiving 2012.

Thanksgiving is a four-day long American holiday dedicated to stuffing yourself with seasonal food and shopping on the Internet in your underwear. So it was no surprise that nobody logged into the chat room at Cheezburger over the long weekend.

Well, until our monitoring systems indicated a total site meltdown. I was pulled in by PagerDuty when the primary contact missed a page because he was in the middle of leftover-food-related activities. He joined me online quickly. We found no evidence of a traffic abnormalities. Our visitors were behaving themselves, not running a DDOS on our servers or anything discourteous like that. The hardware was performing flawlessly. Our database layer seemed to be running a bit more efficiently than usual, if anything. So we turned to the tried-and-true troubleshooter tradition: turning it off and back on again. We ran an IISRESET on each of the web servers.

The site came back up. And stayed up.

We signed off, but I was uneasy with the lack of explanation and kept expecting another outage at any minute. Nothing came, so I scheduled a Five Whys post-outage analysis for Monday afternoon. The Director of Technology, the primary contact, and I set aside an hour to dig into the underlying reasons the site was down over Thanksgiving. But we were stumped at the very first “why?” Why did the entire site go down?

Without any explanation to the first question, there was nowhere deeper we could go. Luckily, we had copious notes from every previous outage postmortem, with a Google Spreadsheet for collation. There were a few outages with similar characteristics. For some reason, one of us decided to look up the time since previous release for each of the outages in this cluster. The data screamed at us:

Our site goes down 80 hours after the last release, σ < 12 hours.

We had been practicing continuous deployment for so long, site stability depended on it.

Finally, we had an answer to our first question. The site went down because nobody releases code over Thanksgiving weekend. At this point, it was the only hypothesis that remained, however improbable. Modus Holmes; Q.E.D.

A geological dig through early layers of the code base revealed an in-memory static variable cache that had been accidentally missed in the switch to centralized cache servers. That cache had functioned, silently and unnnoticed, for aeons in Internet time, until it finally began to stretch the resource boundaries on the web layer. Our load balancer would detect the first server straining, and increase the load on the remaining servers in the cluster, which accelerated their respective cache growth. Eventually a second server would strain, then a third; eventually the cluster would be running at full utilization. The load balancer would have nowhere else to shift the load, the servers would start blocking requests, and the site would finally fall before the mighty torrent of thousands of visitors every second. Only the cadence of continuous delivery prevented that cache from filling up during normal weeks.

We tore out the static cache and replaced it with a centralized one, then resumed our vigilant quest for site stability.

Beginner's Guide to TOTP

I’ve been a long-time fan of two-factor authentication, using Google Authenticator to represent “something I have” in addition to the password, which is “something I know.” So, of course, when GitHub added two-factor authentication, I immediately enabled it on my account.

Last night, unable to sleep, I noticed an update to Google Authenticator in the App Store. This is an app that hasn’t been updated for several years, so I was curious to see what the improvements were. I opened the app, and all of my tokens had vanished. Sure enough, Calvin reported the same issue on Hacker News. This SNAFU, in addition to my growing distrust of Google, inspired me to try and find a new, non-Google app to authenticate. I also decided to learn how it worked. Luckily, the algorithm—the Time-Based One-Time Password Algorithm (TOTP)—is very simple.

Sharing Secrets

First, your authenticator app and the server must agree on a shared secret. The most common way this happens is that the server generates a secret and then displays a QR code to scan.

The QR code for this URI
otpauth://totp/$LABEL?secret=$SECRET

LABEL can be used to describe the key in your app, while SECRET is the 16-character base32-encoded shared secret, which is now known to both the client and the server.

…But Don’t Share With Everyone

Now, a shared secret is a pretty weak method of authentication, because the user can memorize it, effectively making it “something I know.” Also, a man-in-the-middle attack is alarmingly easy: the client will just tell you its secret!

A hash of the secret proves that we know it, without revealing what the secret is, but is still susceptible to a replay attack because the hash of the secret will never change. So an additional “moving factor” is combined with the shared secret in a hash-based message authentication code (HMAC) —specifically HMAC-SHA-1.

In HOTP, a predecessor to TOTP, the moving factor is a simple 8 byte counter. In TOTP, the moving factor is the passage of time! (That’s why it is called the time-based one-time password algorithm.) The two algorithms are otherwise identical; in fact, TOTP is defined as an extension to HOTP.

TOTP uses Unix time (roughtly the number of seconds that have passed since January 1, 1970 GMT) to measure time. Since this would cause a new code to be generated each second, a time step X=30 is defined by default, meaning a new code is only generated every 30 seconds so that users have enough time to type in the code after it has been generated.

Aside: latency and clock skew

The Internet can be slow, and clocks might not match exactly, so some leniency is allowed. RFC6238 recommends looking an extra time step in either direction, which essentially opens the window from 30 seconds to 90 seconds.

Computing a one-time code

The HMAC-SHA-1 algorithm is run with the secret key and the current time step:

HMAC-SHA-1(SECRET, time()/30) = 1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a HS is a 160-bit message authentication code.

We could use this directly, but the user would have to type in an approximately 49-digit number every time they want to authenticate. (Plus, they would have to type this number, error-free, in 30 seconds!)

The code is shortened to 31 bits with dynamic truncation:

The offset is defined as the lower 4 bits of the last byte. The last byte of HS is hex 5a; its lower 4 bits are therefore hex a (decimal ten).

Smoosh together the 4 bytes starting at offset 10:

1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a 0x50ef7f19 DBC1, Dynamic Binary Code #1

And truncate it further to a 31-bit number by making sure the top bit is cleared (in this case, it already is, so the number does not change):

0x50ef7f19 DBC2 (same as DBC1)

Then convert that value to a decimal number:

1357872921 Decimal Code

And show the last 6 digits:

872921

TOTP

The user types this in. The server performs the same calculation and compares the values. If they match, the server knows that the user has possession of the authenticator device, and they can be authenticated. If not, the server knows the current session is invalid. In less than 30 seconds, the time step will increment, and the code will change.

Simple!

With a few simple building blocks, a token-based form of authentication is made using only software. With a password, the user now has two-factor authentication and will be less susceptible to many types of attacks.

JavaScript tail-call optimization decorator using exceptions

JavaScript does not have tail call optimization, at least until proper tail calls arrive in ES6, because of the existence of <function>.caller and <function>.callee. So, if you’re doing something crazy like implementing the lambda calculus from first principles, you’re gonna have a bad time.

Let’s consider a really simple recursive Node.js module, recurse.js:

1 var r = recurse(process.argv[2] || 5000, 0);
2 console.log('found', r);
3 
4 function recurse(n, j) {
5     if(n>0) return recurse(n-1, j+1);
6     else return j;
7 }

We can then test recurse.js to see how many stack frames it takes to crash Node:

 1 var fork = require('child_process').spawn;
 2 var cProcess = 0;
 3 bounds(0, 1000);
 4 
 5 function exec(param){
 6     cProcess++;
 7     return fork(process.argv[0], [process.argv[2], param]);
 8 }
 9 
10 function bounds(min, max) {
11     exec(max).on('exit', function(exitCode) {
12         if(exitCode == 0) {
13             bounds(max, max*2);
14         } else {
15             console.log("Bounds:", min, max);
16             bisect(min, max);
17         }
18     });
19 }
20 
21 function bisect(min, max) {
22     if(min == max)
23     {
24         console.log(min);
25         console.log(cProcess, 'processes created');
26         return;
27     }
28     var sample = Math.floor((min+max)/2);
29     exec(sample).on('exit', function(exitCode) {
30         if(exitCode == 0) {
31             bisect(sample, max);
32         }
33         else {
34             bisect(min, sample);
35         }
36     });
37 }

On my Linode, node bisect.js recurse.js prints:

Bounds: 16000 32000
16376
39 'processes created'

16,376 should be enough stack frames for anyone.

Unless you’re writing in a purely functional style, in which case recursion is the only way you can write a loop.

The way one usually gets out of this situation is by writing in trampoline style. (You can rewrite your compiler or interpreter to automatically use trampoline style, but V8 and Node.js are too big for me to consider it tonight.)

Instead of rewriting my functions, I just want to be able to call a decorator and have them trampoline without manual rewriting. JavaScript exceptions unwind the stack, and can be of any type. So let’s just throw a continuation to the trampoline.

 1 function throwup(fxn) {
 2     return function() {
 3         throw [fxn, Array.prototype.slice.call(arguments)];
 4     }
 5 }
 6 
 7 function makecallable(fxn) {
 8     return function() {
 9         var params = Array.prototype.slice.call(arguments);
10         while(params) {
11             try {
12                 var r= fxn.apply(null, params);
13                 return r;
14             }catch(e){
15                 params = e[1];
16                 fxn = e[0];
17             }
18         }
19     }
20 }
21 
22 module.exports = {makecallable: makecallable, throwup: throwup};

throwup is the tail-call optimizing decorator around the original function; its return value should overwrite the original variable name. makecallable creates the trampoline in the form of a try{…}catch{…} statement.

Here’s an example:

 1 var throwup = require('throw').throwup, makecallable = require('throw').makecallable;
 2 
 3 var recurse = throwup(_recurse);
 4 var callable_recurse = makecallable(_recurse);
 5 var result = callable_recurse(parseInt(process.argv[2], 10) || 5000, 0);
 6 console.log('found', result);
 7 
 8 function _recurse(n, j) {
 9     if(n>0) return recurse(n-1, j+1);
10     else return j;
11 }

And another one, which uses corecursion:

 1 var throwup = require('throw').throwup, makecallable = require('throw').makecallable;
 2 
 3 var even = throwup(_even),
 4     odd = throwup(_odd);
 5 
 6 var r = makecallable(_even)(parseInt(process.argv[2], 10) || 5000, 0);
 7 console.log('found2', r);
 8 
 9 function _even(n,j) {
10     if(n>0) return odd(n-1, j+1);
11     else return ['even', j];
12 }
13 function _odd(n, j) {
14     if(n>0) return even(n-1, j+1);
15     else return ['odd', j];
16 }

I ran node bisect.js tco.js to measure when exception-based tail-call-optimization fails. (I let it run to 32M before getting bored. (It took 2 minutes.))

The stack trace of the exception is allocated on the heap, so it cannot overflow the stack.

Trampolined functions must be called in tail position. Throwing an exception will blow away the surrounding context.

How is performance?

Comically bad.

Here are the results of wrapping the calls to recurse and throwup(_recurse) in for(var i=0;i<100000;i++):

Execution time vs iteration size, looped 100,000 times

Execution time increases drastically for throwup; recurse is less drastic.
Iteration size
  • recurse
  • throwup

Henry Baker describes an unpublished suggestion from Appel to have the compiler (in our case, throwup()) check the stack size and only return to the trampoline if the stack is about to overflow. This method “avoids making a large number of small [but slow] trampoline bounces by occasionally jumping off the Empire State Building.”

Implementation is left to the reader. Don’t forget your parachute!

Cheez Disconnected

My heart sank when Scott made the announcement on the first evening of CheezCon Winter 2012 -- he planned on leaving Cheezburger at the end of the year. Scott represented our department to the board and the rest of the executive team. He hired all but one of us. He built a culture around remote work, and set precedent in lots of subtle ways. For example, around 2010, while we were chatting about something, he proposed a change in the way we referred to teams: instead of naming them after the product manager the developers worked with, why not act like sports organizations and have team names? I thought it was a silly attempt at morale boosting, sort of like allowing posters on the cubicle walls*. But after trying it for a while, I realized that it made sense. A team shouldn't be renamed just because its linebacker retired, it should keep its name season after season. Thus were born Derpakiin, Adventure Team, the Hipsters, Da Bears, and IO. The team names were a great idea.

At the announcement, Scott went around the room and said one nice thing about each person in the department. He told me I was probably the smartest person in the room. (You know what they say about being the smartest person in a room.) I think he said "probably" because Stefan was also in the room, so he couldn't be sure. Just hearing all the short stories reminded me how we were networked together by a single node. Losing the node wouldn't destroy the network, but it would drastically reshape it. I couldn't imagine CheezTech without Scott. He ended up leaving before the year was up, in late November. They are replacing him with a pair of vice presidents.

I left mid-December. Five others left that month. Three more left this January. Half the Scott-CheezTech network has disconnected, and reconnected elsewhere. Motorsports, writing, a winery, events for kids, deep-space mining, consulting, big data, teaching.

There are a lot of smart people still on that network. I will miss visiting everyone in Seattle. I hope we connect again.

* Cheezburger does not have cubicle walls, even in Seattle HQ.

Laziness

Friday, Cam and I were trying to figure out why a new AJAX call was failing for logged out users, but not for logged in users. The exception logs weren't really helpful: somewhere deep inside .NET, the database was timing out after waiting 30s. The code that called the stored procedure is only used in one place in our code, and that code was nowhere near related to the AJAX call we were doing.

I resorted to inspecting the code line-by-line. I almost gave up, when something in the reptilian part of my brain made me reread the return statement:

return Json(new {ti, id = asset.AssetId});

That can't be it. We're just copying the information about the image into an anonymous object to serialize to JSON.

Although, if the serializer threw an exception, the call stack would be the same as the one we observed... I guess this deserves more attention.

But how would this line cause a crash?

asset.AssetId is a long; besides, it's an auto property, and those are safe.

ti is a TemplateImage. That is one of the earliest classes ever written at Cheezburger. It can't be crashing, can it?

Then I realized that TemplateImage had a User property, User had a UserStats, and UserStats had a property that called the stored procedure that was timing out. Suddenly it all made sense!

The JSON serializer was doing its job, making a deep copy of ti by traversing field and property members as deep as it could. Unfortunately, the UserStats properties were lazy-loaded from the database, and there were no attributes telling the serializer to skip them.

I've gradually begun to dislike lazy attributes because of how easy it is to forget how much work is going on under the covers. Now I'm convinced that this laziness is evil.

Why was this only affecting logged out users?

If you're logged out, the User object is set to a default value. When the database was queried for statistics, it was calculating those statistics across all the images that have ever been made by any logged out user. That's an awful lot of data to chug through.

What was the fix?

The quick fix is to serialize the one property we needed from ti, Url:

return Json(new {ti = new {ti.Url}, id = asset.AssetId});

I didn't have to change the Javascript receiver, and as a bonus, much less data is being sent over the wire. Later, I will refactor UserStatistics to get rid of the lazy properties.

How to get a job at Cheezburger

How do I get a job at icanhascheezeburger?

Well, first you remove that extraneous 'e' from icanhascheezburger. Also, it's generally considered disrespectful to call a company by one of its product names. That's like sending a letter to Cupertino asking for a job at MacBook.

OK, I get it, you like to hear the sound of yourself blogging. How do I get a job at Cheezburger?

You apply on the Cheezburger Job Board. Since you're asking me, I assume you're thinking about applying for the job "ASP.NET/C# Developer", which I am familiar with. Luckily, most of the requirements are right there on the job page, tersely.

Can you explain the requirements?

Experience in ASP.NET MVC
We don't have a lot of time to spend training people how to operate the tools we use. That being said, if you have experience in pretty much any web MVC framework, you will be able to pick up ASP.NET MVC in a day or two.
Experience developing consumer facing web sites
Cheezburger serves hundreds of thousands of requests a minute. Has your code ever faced the Internet... and survived?
Experience with Agile development practices
Some of our requirements are basically keyword matching. This is one of them. Is there any evidence that you have ever attended a scrum? Do you know the difference between pigs and chickens? Have you ever used XP (not from Microsoft)? I need to see that in your application.
A knack for consensus building through teamwork and collaboration
Blah blah buzzword blah blah leadership and communication. Do you have smart ideas? Of course you do, everyone does. The hard part is getting other people to listen to your ideas. How do you lead? Leadership is not the same thing as being a manager. Our intern last summer had some great ideas that took some coaxing, but eventually he was able to convince the team to change a very important process.
Passion for programming and making awesome software
I need you to be excited about your job. If you don't love your craft, I can't get excited about working with you.
A degree in computer science or related field
This is such a weak signal that I don't even bother checking for it when I screen resumes. I am really glad I got my double major in Software Engineering and Computer Science, but I don't think it's the only way people can learn how to make quality software.
Passion for programming and making awesome software
Since this is on our job posting twice, you should take that as a sign that you really need to make your passion clear when you apply.
Excellent written and verbal communication skills
My instructions are to read your cover letter and look for spelling and grammar mistakes. Does it look like you know how to communicate well? If you don't have a cover letter, I can't give you this point. Please write a paragraph or two that is not just a prose form of your resume. Tell me a story. You can even use this cover letter to nail the other requirements down, such as motivation or passion for programming.
Okay, that's pretty much it for the obvious requirements. Do you want to hear the secret requirements?

There are secret requirements?

Well, not after I hit "publish". But if you took the time to ask Siri, "how do I get a job at Cheezburger?", you've already proven yourself to be miles ahead of most of your job-seeking cohort. Therefore, I don't mind sharing these secrets with you.
Cheezburgler
I need some proof that you've used our sites and have a general feel for our different areas. Currently we have the Cheezburger Network blogs (such as I Can Has Cheezburger?, Memebase, and FAILBlog), Cheezburger Sites (create your own!) and Know Your Meme. A really easy way to get this point is to create a Cheezburger profile, play around with it, and fill in the box that says "My Cheezburger.com account username is..." It also helps to have at least a passing understanding of some popular Internet memes.
Hard core
Have you done anything that other developers would think of as hard? Some examples: wrote a real program in assembly, a device driver, an operating system, an interpreter. I need to know that you understand von Neumann architecture and recursion.
Motivation/Self-Directed Worker
This point is hard to discern, so you get goldstar.gif stamped on your resume if I see anything that tells me you can do work without somebody reminding you to.
Different
This point isn't asking about your religion, appearance, gender, sexual identity, political preference, veteran status, or other protected class. First, that's illegal. And second, we honestly don't care. What's important is a diversity of experience. This point is in direct competition with the "public facing web sites" requirement, because we also want to hire people who have done things that aren't public facing web sites. Did you write HFT algos for Morgan Stanley? Firmware for a racecar? Robotic control systems? A popular Flash game? Pizza delivery software? These types of experience mean you bring a fresh perspective to our problems.

That sounds like a whole lot of work.

Yeah, but this kind of thought will help you find out what kind of team you want to be a part of. You'll spend a whole lot of time at work - isn't it worth spending an hour tweaking your resume to make sure that time is spent exactly where you want to spend it?

I can has job?

Cute. You get the Cheezburgler point. The job page has everything else you need.

 

Specialization is organizational growth

A company cannot grow without specialzation. There are a certain number of logical roles that must be filled: design, cleaning bathrooms, testing, sales, accounts payable, shipping, bills, hiring, administration, development, etc. In a brand-new company, one physical person may fill a large number of roles (in the simplest case, the sole founder fills all the roles).

Single founder

A good analogy is a building made of blocks. Each space on the ground is a role that the company needs to fulfill. The area of this footprint is limited on all sides, so the only way to build is up. The company hires new people to take over some of those responsibilities so the founders can focus on the things that provide the most value.

First hires

Each employee provides a certain number of blocks to the company's volume. They can spread their blocks around multiple roles, with limited height, or spend them all in a single column by dedicating their efforts to a single role.

A new employee takes over one or more roles that a previous employee already filled, giving the previous employee their blocks back to reallocate. The only realistic way to go is up, by specializing.

Specializing

One implication is that you can't create new roles for people. All you can do is recognize where people are overstretched or poor fits for a specific role, and fill in the gaps. Another is that uneven specialization is risky, as it creates spiky towers that are unsupported by the others around them. But uneven specialization allows the highest point on the tower to rise higher than otherwise possible with a given volume.

Mucking Calls

Programming involves a lot of the same motions as horse stall mucking. We're all just shoveling parameters from one function call to the next. Occasionally, I can even see through a hole in the barn the types of work we are meant to be doing: galloping through the bluegrass.

Steve Jobs often said computers are like a bicycle for the mind. He was right, but only because we are still in the penny-farthing era of computation. My mind can go faster than this, I know it. I just don't know how the supersonic jet of the mind works, yet.

GitHub Stack Overflow LinkedIn