Я щойно отримав роботу розробника в Snapchat.

Близько року тому, перебуваючи в Іраці офіцером армії, я почав кодувати для розваги. (Ви можете прочитати всю історію тут.) Ну, після довгих занять я влаштувався на першу роботу інженером-програмістом у Snapchat (Snap) на Венеціанському пляжі.

Пошук роботи був непростим. Я стикався з багатьма відмовами, хибними підказками та моментами сумнівів. Але досвід допоміг мені розробити ментальну основу для наближення до діяльності, яка має високу ймовірність успіху в довгостроковій перспективі, але мала ймовірність успіху в будь-який день - діяльність, як пошук першої роботи інженером-програмістом.

Оскільки знайти мою конкретну роботу здебільшого завдяки великій удачі (вдалий час, випадковий зв’язок, хороший рік фінансування стартапів у Лос-Анджелесі), окреслення конкретних кроків, які я зробив, не буде для вас надто корисним. Це тому, що я робив ті самі речі, що всі вам кажуть:

  • будувати побічні проекти
  • вирішувати практичні завдання
  • побудувати свою мережу
  • і подати заявку на тонну робочих місць

Дії, які ви робите, та акцент, який ви робите на кожному, будуть сильно відрізнятися залежно від вашої особистості та конкретних обставин. Тим не менш, ментальні рамки, до яких я прийшов під час пошуку роботи, можуть допомогти вам незалежно від ваших обставин.

Тож я збираюся поділитися процесом мислення, який в кінцевому підсумку призведе до мого розумового середовища, одночасно даючи вам швидкий вступ до динамічного програмування одночасно. Сподіваюся, це вам стане в нагоді ?!

Як працює типовий пошук роботи розробника

Шукаючи свою першу роботу з програмування, я прочитав чимало особистих записів про те, як інші програмісти-самоучки та студенти початкового табору знайшли свою першу роботу. З їхніх історій пошук роботи здавався дуже послідовною моделлю:

  1. навчитися кодувати
  2. відточити свої вміння
  3. зробити деякі мережі
  4. робота над практичними проблемами
  5. застосовувати до робочих місць
  6. співбесіда
  7. отримати пропозиції про роботу

З точки зору структури даних, я зобразив це як обхід вузлів пов'язаного списку.

Я думаю, що загальним недоліком, коли люди розповідають про свої спогади (особливо якщо вони працювали інженером вже деякий час), є те, що вони надто акцентують увагу на причинно-наслідкових зв’язках між конкретними діями, які вони вчинили, і результатом, що стався :

Я зробив А, потім сталося Б. Тому А викликав Б.

Оскільки вони мають перевагу огляду назад, їх результат видається детермінованим. Якщо ви лише виконуєте ті самі кроки, ви знайдете хорошу роботу.

Так. І ні. З мого досвіду, у довгостроковій перспективі , якщо ви справді прагнете програмувати і постійно наполягаєте на поліпшенні, ви врешті знайдете роботу, гідну ваших навичок (незалежно від того, чи маєте ви ступінь з інформатики в певній школі в Пало-Альто). Попит на інженерів програмного забезпечення реальний і лише зростає. Але в короткостроковій перспективі цей процес є надзвичайно випадковим і базується на безлічі змінних, які ви не можете бачити чи контролювати: потреби у наймі компанії, ринкові тенденції, те, для чого наймаються компанії, що займаються найвищими технологіями.

Коли я почав шукати роботу в Лос-Анджелесі, я надіслав купу заявок, намагаючись щось знайти -нічого. Я кодував би в обмін на безкоштовну їжу та футболки, якби хтось запропонував мені таку можливість. Ось кілька ранніх відповідей, які я отримав:

Ви пишете приємний чистий код Javascript. І ви були супер доброзичливими, і нам сподобалось з вами поговорити. Однак ми не бачили, що ви кодуєте так продуктивно, як нам потрібно. Щоб рухатися вперед з молодшими кандидатами, нам потрібно побачити виняткову сильну сторону, і ми не бачили достатньо такої сили у вас на даний момент. Це означає, що ми не можемо з вами працювати. Ми всі дуже високо думаємо про вас, і кожен із задоволенням брав у вас інтерв’ю, твердо переконавшись, що ваш потяг, трудовий настрій та природна цікавість - це саме те, що ми прагнемо до кандидата. На жаль, враховуючи часові рамки, де ми знаходимось логістично, ми шукаємо когось із більш сучасним досвідом у розробці інтерфейсів. Вибачте за всі затримки. Цей процес є більш складним, ніж я передбачав.Я повідомлю вас десь наступного тижня, коли ми наблизимось до прийняття рішення.

Потім [тиша] протягом багатьох тижнів.

Ну це були банани. Я зробив завдання кодування, яке зайняло у мене 6 годин, і компанія навіть не може надіслати мені відповідь по електронній пошті?

Отримання кожного з цих електронних листів (і численних невідповідей теж) було для мене дуже болючим досвідом. Але ніколи не втрачайте можливості навчитися чогось корисного з труднощів . Показуючи вам процес мислення, який надихнув мій пошук роботи, сподіваємось, ця стаття дасть вам інструмент для оптимізації вибору, який ви робите під час пошуку роботи, і дасть вам натхнення продовжувати рухатись до своєї мети.

"Біль неминучий, страждання необов'язкові" - Харукі Муракамі

Проблема рюкзака

Дозвольте мені проілюструвати кроки, які я зробив, щоб дійти до мого ментального середовища, використовуючи варіацію загального запитання про співбесіду в галузі комп’ютерних наук: проблема рюкзака.

** ОНОВЛЕННЯ: Я розмістив свій код у репозиторії GitHub з невеликим набором тестів, що дозволяє вам пограти з кодом і самостійно розробити рішення. **

Ось проблема:

У вас є набір заходів, які ви можете зробити, щоб збільшити свої шанси знайти роботу. Кожна діяльність займає певний час, але надає певний досвід. У нас є лише обмежений час для підготовки до пошуку роботи, тому ми не можемо зробити все. Наша мета - максимально збільшити кількість очок досвіду, вибравши оптимальний набір заходів.

Як ви пишете функцію, яка вибере оптимальний набір заходів із переліку доступних видів діяльності та обмеженого часу?

Рішення 1: Груба сила

Повторно вирішивши проблему, ви хочете вибрати набір дій, які:

  1. Виконується така кількість часу, яка менше або дорівнює загальному часу, який у вас є
  2. Максимізує повернені очки досвіду (XP)

Найінтуїтивніший спосіб - використовувати той самий алгоритм, який ми використовували б у повсякденному житті. Ми спробували б різні комбінації заходів, перевіряючи, чи відповідає це обмеженню пристосування протягом обмеженого часу. Ми продовжували б шукати всі можливі комбінації та вибирати ту, яка максимізує XP.

Ось код для цього алгоритму:

Проблема полягає в тому, що цей підхід насправді є складним щодо часу, тобто, коли збільшується розмір нашого вкладу (кількість видів діяльності, які ми могли б вибрати), кількість часу, необхідного для обчислення рішення, збільшується набагато швидше.

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.

Якщо ви шукаєте свою першу роботу в програмуванні, я сподіваюся, читати це було для вас корисним чи, принаймні, надихаючим - дивіться, такий бездарний дуфер, як я, знайшов чудову роботу! Успіху там, і я хотів би закінчити, поділившись найкращою порадою, даною мені під час пошуку роботи:

“Не турбуйтеся про те, чи достатньо ви добре, а про те, чи подобається вам програмування і чи готові ви докласти зусиль. Якщо ти зробиш ці дві речі, ти це зробиш ". - перефразоване з Едгара Пабона в підкасті Breaking Into Startups

Дякуємо за читання та успіхів у пошуку роботи!