Ich habe gerade einen Entwicklerjob bei Snapchat bekommen.

Vor ungefähr einem Jahr, als ich als Offizier im Irak stationiert war, begann ich zum Spaß zu programmieren. (Sie können die ganze Geschichte hier lesen.) Nun, nach vielem Studium bekam ich meinen ersten Job als Software-Ingenieur bei Snapchat (Snap) in Venice Beach.

Die Jobsuche war nicht einfach. Ich sah mich vielen Ablehnungen, falschen Hinweisen und Momenten des Zweifels gegenüber. Aber die Erfahrung hat mir geholfen, einen mentalen Rahmen für die Annäherung an Aktivitäten zu entwickeln, die langfristig eine hohe Erfolgswahrscheinlichkeit, aber an einem bestimmten Tag eine geringe Erfolgswahrscheinlichkeit aufweisen - Aktivitäten wie die Suche nach einem ersten Job als Softwareentwickler.

Da das Finden meines speziellen Jobs hauptsächlich auf viel Glück zurückzuführen war (gutes Timing, eine zufällige Verbindung, ein gutes Jahr der Finanzierung für Startups in Los Angeles), wäre es für Sie nicht besonders nützlich, die spezifischen Schritte zu skizzieren, die ich unternommen habe. Das liegt daran, dass ich die gleichen Dinge getan habe, die Ihnen jeder sagt:

  • Nebenprojekte bauen
  • Übungsprobleme lösen
  • Bauen Sie Ihr Netzwerk auf
  • und bewerben Sie sich für eine Menge Jobs

Die Maßnahmen, die Sie ergreifen, und die Betonung, die Sie auf die einzelnen Maßnahmen legen, hängen stark von Ihrer Persönlichkeit und den spezifischen Umständen ab. Der mentale Rahmen, zu dem ich bei meiner Arbeitssuche gelangt bin, kann Ihnen jedoch unabhängig von Ihren Umständen helfen.

Ich werde also den Denkprozess teilen, der letztendlich zu meinem mentalen Rahmen führt, und Ihnen gleichzeitig eine kurze Einführung in die dynamische Programmierung geben. Ich hoffe du findest das nützlich ?!

So funktioniert eine typische Jobsuche für Entwickler

Auf der Suche nach meinem ersten Programmierjob habe ich einige persönliche Berichte darüber gelesen, wie andere autodidaktische Programmierer und Bootcamp-Absolventen ihre ersten Jobs gefunden haben. Aus ihren Geschichten ging hervor, dass die Jobsuche ein sehr sequentielles Modell war:

  1. Codieren lernen
  2. Schärfen Sie Ihre Fähigkeiten
  3. Vernetzen Sie sich
  4. Arbeit an Übungsproblemen
  5. auf Jobs bewerben
  6. Interview
  7. Stellenangebote erhalten

In Bezug auf die Datenstruktur stellte ich mir das Durchlaufen von Knoten einer verknüpften Liste vor.

Ich denke, ein häufiger Fehler, wenn Menschen ihre Erinnerungen erzählen (insbesondere wenn sie eine Weile als Ingenieur gearbeitet haben), ist, dass sie zu viel Wert auf Ursache-Wirkungs-Beziehungen zwischen den spezifischen Maßnahmen, die sie ergriffen haben, und dem Ergebnis, das aufgetreten ist, legen ::

Ich habe A gemacht, dann ist B aufgetreten. Daher verursachte A B.

Weil sie im Nachhinein hat, ihr Ergebnis scheint deterministisch . Wenn Sie nur die gleichen Schritte ausführen, werden Sie einen guten Job finden.

Ja. Und nein. Nach meiner Erfahrung werden Sie auf lange Sicht , wenn Sie sich wirklich dem Programmieren verschrieben haben und sich ständig bemühen, besser zu werden, irgendwann einen Job finden, der Ihren Fähigkeiten würdig ist (unabhängig davon, ob Sie einen Abschluss in Informatik von einer bestimmten Schule in haben Palo Alto). Die Nachfrage nach Softwareentwicklern ist real und wächst nur. Aber in der kurzfristig , ist der Prozess Super zufällig und basiert auf vielen Variablen , dass Sie keine Sichtbarkeit auf oder Kontrolle über: Unternehmen Einstellungsbedarf, Markttrends, was Hip - Technologien Unternehmen derzeit die Einstellung für.

Als ich in Los Angeles nach Jobs suchte, schickte ich eine Menge Bewerbungen, um etwas zu finden -etwas. Ich hätte im Austausch für kostenloses Essen und T-Shirts codiert, wenn mir jemand die Gelegenheit geboten hätte. Hier sind einige der ersten Antworten, die ich erhalten habe:

Sie schreiben schönen sauberen Javascript-Code. Und du warst super freundlich und wir haben es genossen mit dir zu reden. Wir haben jedoch nicht gesehen, dass Sie so produktiv codieren, wie wir es brauchten. Um mit Nachwuchskandidaten voranzukommen, müssen wir eine außergewöhnliche Stärke sehen, und wir haben zu diesem Zeitpunkt nicht genug von einer solchen Stärke bei Ihnen gesehen . Dies bedeutet, dass wir nicht mit Ihnen zusammenarbeiten können. Wir alle schätzen Sie sehr und haben es genossen, Sie zu interviewen, mit der festen Überzeugung, dass Ihr Antrieb, Ihre Arbeitsmoral und Ihre natürliche Neugier genau das sind, was wir von einem Kandidaten erwarten. Angesichts des Zeitplans, in dem wir uns logistisch befinden, suchen wir leider jemanden mit aktuellerer Erfahrung in der Front-End-Entwicklung. Entschuldigung für all die Verzögerungen. Dieser Prozess ist komplizierter als ich erwartet hatte.Ich werde Sie nächste Woche aktualisieren, wenn wir uns einer Entscheidung nähern.

Dann [Schweigen] für viele Wochen.

Nun, das waren Bananen. Ich habe eine Codierungs-Challenge durchgeführt, die 6 Stunden gedauert hat, und das Unternehmen kann mir nicht einmal eine Antwort-E-Mail senden.

Es war für mich eine sehr schmerzhafte Erfahrung, jede dieser E-Mails (und auch die zahlreichen Nichtantworten) zu erhalten. Aber verpassen Sie niemals die Gelegenheit, etwas Nützliches aus der Not zu lernen . Dieser Artikel zeigt Ihnen hoffentlich den Denkprozess, den meine Jobsuche inspiriert hat, und gibt Ihnen ein Werkzeug, mit dem Sie die Entscheidungen, die Sie während der Jobsuche treffen, optimieren können, und gibt Ihnen Inspiration, um weiter auf Ihr Ziel hinzuarbeiten.

"Schmerz ist unvermeidlich, Leiden ist optional" -Haruki Murakami

Das Rucksackproblem

Lassen Sie mich anhand einer Variation einer häufig gestellten Frage zum Informatik-Interview die Schritte veranschaulichen, die ich unternommen habe, um zu meinem mentalen Rahmen zu gelangen: das Knapsack-Problem.

** UPDATE: Ich habe meinen Code in ein GitHub-Repo mit einer kleinen Testsuite eingefügt, damit Sie mit dem Code herumspielen und selbst eine Lösung entwickeln können. **

Hier ist das Problem:

Sie haben eine Reihe von Aktivitäten, die Sie auswählen können, um Ihre Chancen auf einen Arbeitsplatz zu erhöhen. Jede Aktivität nimmt eine bestimmte Zeit in Anspruch, bietet jedoch eine gewisse Erfahrung. Wir haben nur begrenzte Zeit, um uns auf die Jobsuche vorzubereiten, daher können wir nicht alles tun. Unser Ziel ist es, die Anzahl der Erfahrungspunkte durch Auswahl der optimalen Aktivitäten zu maximieren.

Wie schreibt man eine Funktion, die aus einer Liste verfügbarer Aktivitäten und einer begrenzten Zeitspanne den optimalen Satz von Aktivitäten auswählt?

Lösung 1: Brute Force

Wenn Sie das Problem erneut ausführen, möchten Sie die folgenden Aktivitäten auswählen:

  1. Dies dauert weniger als oder gleich der Gesamtzeit, die Ihnen zur Verfügung steht
  2. Maximiert die zurückgegebenen Erfahrungspunkte (XP)

Am intuitivsten ist es, denselben Algorithmus zu verwenden, den wir im täglichen Leben verwenden würden. Wir würden verschiedene Kombinationen von Aktivitäten ausprobieren und prüfen, ob sie innerhalb einer begrenzten Zeit unserer Anpassungsbeschränkung entsprechen. Wir würden weiterhin alle möglichen Kombinationen durchsuchen und die auswählen, die XP maximiert.

Hier ist der Code für diesen Algorithmus:

Das Problem ist, dass dieser Ansatz in Bezug auf die Zeit sehr komplex ist , was bedeutet, dass mit zunehmender Größe unseres Inputs (Anzahl der Aktivitäten, die wir möglicherweise auswählen könnten) die Zeit, die zur Berechnung einer Lösung benötigt wird, viel schneller zunimmt.

If we have 6 possible activities, we start by creating every possible combination with a single activity, giving us 6 combinations that contain one activity.

Then we have to create all possible combinations with 2 activities. For each of the original 6 combinations, we have to create a combination with each of the 5 remaining activities (you can only do each activity once).

Then to create all possible combinations with 3 activities, we have to take each of our combinations containing 2 activities and create a combination with each of the 4 remaining activities.

Eventually we’ll have something that looks like (6 * 5 * 4 *3 * 2 * 1), which is O(n!). Also, because we sum all the items in each combination every time to calculate the total time and XP, our end time complexity is O(n! * n).

Imagine that instead of running this algorithm on a computer that can execute trillions of operations a second, you have to run it on your limited brain, which actually takes 10 hours (in a very optimistic world) to do a side project to learn a new JavaScript MV* framework.

And also instead of a choice of 6 activities, you have thousands of possible things you could be doing to prepare for job search. (Just look up “how to code” on Google).

It is completely impractical to try every possible combination of activities to prepare yourself for job search. The lesson from this example is there is an almost infinite amount of things you could be doing that will increase your chances of finding a job, but you can’t try all of them. You need a better method to determine your optimal set of activities.

Backtracking

Obviously, as programmers (and hackers ?), we’re going to want to optimize our current solution somehow.

Let’s try the BUD approach from Cracking the Coding Interview by Gayle McDowell (an awesome prep resource, even if your job interviewers never ask algorithmic questions).

  1. What Bottlenecks does our brute force solution have?

When looking for the bottleneck, we’re usually trying to identify the most complex part of the process, i.e. the n! part of of our O(n! * n) algorithm.

The bottleneck, or most complex part of our job search problem is the fact that we have to dynamically create many different combinations and try them out. Every time we add another option, we have many more possible combinations to try out.

Now I have to admit I kind of led you down a false road. My job search problem, as a variation on the Knapsack Problem, is part of a set of problems called NP-Hard. In short, problems are NP-Hard when there is no known efficient way to solve the problem, or verify that that a solution to a problem is correct. So unless you’re a world changing computer scientist, you’re probably not going to figure out an objectively efficient way to combine all the activities.

But that’s ok!!! Sometimes, in interviews and job search, we follow false leads. As long as we learn something from the process, we haven’t really wasted time. Even if we can’t find an overall efficient way to solve the problem, we can still find a more efficient way that we’re currently using.

So let’s move on.

2. Is my algorithm doing Unnecessary work or Duplicated Work?

This is where we can make major gains on our solution.

One thing we should change is that for every possible combination, we have to iterate through all the activities in the set to calculate the total XP and total time from that set of activities. This is duplicated work, because we’re adding up the same values over and over.

If we just saved the total XP and time of the combination in a variable, we could just add the XP and time of each new activity we add to to the total. This would take our solution from O(n! * n) to O(n!).

This is helpful, but doesn’t fundamentally make our problem too much faster to run.

What other optimization could we do?

We’re also calculating a lot of combinations that could not possibly lead to a valid solution. This is unnecessary work.

For reference here is the list of activities again:

const ACTIVITIES = [ {name: 'side-project', time: 10, xp: 12}, {name: 'algorithms', time: 3, xp: 7}, {name: 'networking', time: 1, xp: 0.5}, {name: 'exercise', time: 2, xp: 1.5}, {name: 'systems design', time: 4, xp: 4}, {name: 'making CSS codepens', time: 3, xp: 4}];

Let’s say we have 8 hours total to prepare for our job search. How would our brute force algorithm check combinations?

Based on the order of the ACTIVITIES array, we would first consider a set just including the side-project object. There is no valid solution containing the side-project activity because it takes 10 hours to do and we only have 8 hours total.

But our brute force algorithm (being brute force) doesn’t know that, and will then check every possible combination we can create with side-project.

So it will check if [side-project, algorithms] is a valid solution. It is not.

And it will check if [side-project, algorithms, networking] is valid. It is not.

And it will check if [side-project, algorithms, networking, exercise] is valid. It is not.

See how much unnecessary work we’re doing?

What if we could give our algorithm a little bit of intelligence, so it can check if our current state (the activities we currently have selected) can lead to a valid solution? If the activities we currently have selected can lead to a valid solution (specifically, if our selected set of activities takes less or equal time than the total time we have as a parameter to the function) then we keep selecting new activities and checking if they’re valid.

If not, we stop and unselect the last activity we selected.

For example, if we have 8 hours total, we will first check to see if a combination containing just side-projects can possibly lead to a valid solution. As we determined before, it cannot, because it takes up more time than we currently have.

So we unselect side-projects, and try out different combinations starting with algorithms. By checking to see if our current selected activities could lead to a valid solution, we’re avoiding having to check any of the combinations containing side-projects, because they could not possible lead to a valid solution.

This approach is called backtracking. We check to see if where we are could lead to a valid solution, if not, we go back one step and try to make a different choice.

Here is the code:

This solution implements the two optimizations that we discussed earlier:

  1. Keeping track of total XP and time so we can calculate it in O(1) instead of summing the entire set every time in O(n)
  2. Checking whether our current set will lead to a valid solution before we recursively add a new item

While backtracking saves a lot of work it doesn’t really reduce the overall runtime complexity of our algorithm. It’s still O(n!), because we’re still recursively checking most possible combinations.

But implementing the backtracking algorithm has probably given you a clue on how to continue working on the problem. In the brute force solution, we had to assemble and check the entire combination for each possible combination. With backtracking, we get to check if the path we’re on will lead to a valid solution, before we assemble the entire combination.

Hmmmmm…..

Is there a way to consider only whether or not we should add another activity to our set? This would be a much easier problem than trying to create the entire combination at once. It would allow us to break up our hard problem (finding the optimal combination) to a series of smaller problems (deciding whether or not to add a single activity).

Dynamic Programming

Dynamic programming is a method where we can divide our big optimization problem (what combination of activities should I choose?) into a series of manageable decision problems (should I include this activity in my optimal solution or not?). We divide and conquer.

Dynamic programming is a common way to solve NP-Hard problems like the Knapsack problem, and coincidentally also a good way to think about job search. It’s hard to determine what combination of activities will make you ready for job search. There’s no efficient way to find the optimal combination or to check if your current choice is optimal.

But it’s a lot easier to break your time period down into individual days and weeks, and try to figure out which activities you should be doing for each small period of time.

To solve our job search problem using dynamic programming, we break the problem up into a series of smaller problems (how do I optimize a smaller period of time?) and then take the solution from each of the smaller problems and combine them into a larger solution.

Sounds confusing? Let’s walk through it:

const ACTIVITIES = [ {name: 'side-project', time: 10, xp: 12}, {name: 'algorithms', time: 3, xp: 7}, {name: 'networking', time: 1, xp: 0.5}, {name: 'exercise', time: 2, xp: 1.5}, {name: 'systems design', time: 4, xp: 4}, {name: 'making CSS codepens', time: 3, xp: 4}];

What’s the optimal solution if we have a total time of t=0 (zero) to prepare?

If we have zero time, we can’t do any activities, so return an empty set, [].

Ok, now what’s the optimal solution is we have a total time of t=1?

First, let’s see what activities are possible to do: we can’t do a side-project (time t=10) or study algorithms (time t=3). The only thing we can do is networking (time t=1).

So we need to decide if adding networking to the optimal solution for time t=0 will lead to an optimal solution.

If we add networking, we come out with a total XP of 0.5, not bad.

If we don’t add networking, we can’t really do anything else, so we come out with a total XP of 0.

0.5 is still better than 0, so if we only have a total time of t=1, we should do networking. The optimal solution for time t=1 is [networking]

What’s the optimal solution for time t=2?

What activities are possible with time t=2, that we haven’t already considered? Just exercise.

If we choose to add exercise, which takes time t=2, we no longer have any time to do anything else, so our solution is [exercise], which leads to 1.5 XP.

We compare the optimal solution including exercise (which leads to 1.5XP) and the optimal solution not including exercise (which leads to 0.5XP). Since the solution containing exercise is better, we choose that one (In real life, I also feel that with very limited time, some self-care is always more useful than more prep ?).

Now here is where it gets really interesting: What’sthe optimal solution for time t=3?

Again, what activities are possible for time t=3?

We have the option to choose from [algorithms, exercise, networking].

If we choose algorithms which takes time t=3, we have no time to do anything else, so one possible solution is [algorithms].

If we choose exercise which takes time t=2, we have t=1 time left to do something else? How do we know what to choose for the remaining time?

We know the optimal solution for time t=1, is [networking], so we don’t have to calculate it again. We know we can’t do better than the optimal solution for time t=1.

So one possible solution is [exercise, networking].

Again we compare all the possible solutions and see that the best we can do is [algorithms].

This is the basic structure of a dynamic programming solution: at each amount of time, we test the decision of whether or not to add a specific activity. We compare all possible solutions, and figure out the optimal one.

Solutions for greater amounts of time build upon the optimal solutions for the same problem with a smaller amount of time. This allows us to call the dynamic programming function recursively.

For my example I chose to sort the array of activities by the time it takes to complete them (least to greatest). This allows us the quickly determine which items are possible in the given time because they are sorted by time.

Below is the code:

Wooooo! If you made it through that example the first time, then you’re a way faster learner than I am. I hope it was an interesting in finding alternate ways to solve hard algorithmic questions!

Finally, what is the purpose of this series of three examples you might ask?

Not only did I stealthily give you some practice working on a question very similar to the ones you might be asked in technical interviews, I showed you the steps that I took to come to my mental framework.

There are an almost infinite combinations of activities you could be doing, and there’s no efficient way to determine the optimal set of activities you should do. A lot of paths don’t lead to a valid solution, just like a lot of job applications and interviews won’t lead to a job.

You could try every possible combination of job search activities (brute force), but since we are human beings with finite time, this isn’t an efficient way to arrive at our goal.

We could optimize our approach by evaluating at each step whether or not our approach will lead to our goal (backtracking). For example, if we are constantly reaching out to third-party recruiters to help us find job leads, and the recruiters haven’t been very helpful in generating interviews, maybe we should backtrack and consider a different activity.

Lastly, since job search is not a one day affair, we could try to optimize each day and combine days together (dynamic programming). This way we have a manageable problem to deal with (should I study algorithms today?) versus a really hard one (what should I do for the next month to prepare myself for job search?).

Finally, I just want to point out that with all 3 approaches, even though they were not objectively efficient, we did eventually reach a solution. While in the middle of job search, it’s really important to remember that in the long term, you will achieve your goal, and to keep pushing forward each day.

My advice for handling your developer job search

I’m going to succumb to my temptation to give you two pieces of advice from my experience.

  1. It’s super hard to judge your own performance during interviews and coding challenges — so just focus on the process. You won’t know during the interview or immediately afterward whether you’re doing well or poorly.
  2. Success or failure are fleeting and shouldn’t determine your happiness.

Wenn Sie nach Ihrem ersten Programmierjob suchen, hoffe ich, dass das Lesen für Sie nützlich oder zumindest inspirierend war - schauen Sie, ein talentloser Duffer wie ich hat einen großartigen Job gefunden! Viel Glück da draußen und ich möchte zum Schluss die besten Ratschläge geben, die mir bei meiner Jobsuche gegeben wurden:

„Mach dir keine Sorgen, ob du gut genug bist, mach dir keine Sorgen darüber, ob du gerne programmierst und ob du bereit bist, hart genug zu arbeiten. Wenn du diese beiden Dinge tust, wirst du es schaffen. “ - paraphrasiert von Edgar Pabon im Podcast Breaking Into Startups

Vielen Dank fürs Lesen und viel Glück bei Ihrer Jobsuche!