<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[tomdev's notes]]></title><description><![CDATA[hey im tom de villiers otherwise known as tomdev, im a software engineer at amazon. these are my notes on software engineering, systems, learning at scale, what i did right, what i'd do differently and occasionally my thoughts on life]]></description><link>https://www.tomdev.blog</link><image><url>https://substackcdn.com/image/fetch/$s_!-7Mv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe5fff54-446a-4ce9-8a75-ecf69a4985ec_500x500.png</url><title>tomdev&apos;s notes</title><link>https://www.tomdev.blog</link></image><generator>Substack</generator><lastBuildDate>Fri, 17 Jul 2026 10:51:31 GMT</lastBuildDate><atom:link href="https://www.tomdev.blog/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Thomas De Villiers]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[tomdev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[tomdev@substack.com]]></itunes:email><itunes:name><![CDATA[Tom De Villiers]]></itunes:name></itunes:owner><itunes:author><![CDATA[Tom De Villiers]]></itunes:author><googleplay:owner><![CDATA[tomdev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[tomdev@substack.com]]></googleplay:email><googleplay:author><![CDATA[Tom De Villiers]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[i taught a computer to make up names using a neural network]]></title><description><![CDATA[bigrams, probability matrices, and the dumbest thing that could possibly work]]></description><link>https://www.tomdev.blog/p/i-taught-a-computer-to-make-up-names</link><guid isPermaLink="false">https://www.tomdev.blog/p/i-taught-a-computer-to-make-up-names</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Wed, 08 Jul 2026 08:49:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7JZN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;ve been learning about neural networks and I wanted to take you along with me building a neural network to generate new names that seem real but don&#8217;t exist yet. The neural network will be trained on a list of about 32,000 names that is publicly available on the internet. It will then be able to output new names based on its training data. That is the goal and it&#8217;s not going to be easy so this is part 1 of the series.</p><p>my goal is to start generating names using bigram frequency, and then in part 2 we will start building a single layer neural network to do the same thing.</p><p>So where do we begin?</p><p>Let&#8217;s take a look at the dataset we are working with</p><pre><code>words = open('names.txt', 'r').read().splitlines()
words[:10]

[&#8217;emma&#8217;,
 &#8216;olivia&#8217;,
 &#8216;ava&#8217;,
 &#8216;isabella&#8217;,
 &#8216;sophia&#8217;,
 &#8216;charlotte&#8217;,
 &#8216;mia&#8217;,
 &#8216;amelia&#8217;,
 &#8216;harper&#8217;,
 &#8216;evelyn&#8217;]</code></pre><p>pretty simple, one name per line, lets get some more info next</p><pre><code><code>len(words)
32033

min(len(w) for w in words)
2

max(len(w) for w in words)
15</code></code></pre><p>okay cool so there is about 32,000 names in the training data, the shortest name is 2 letters long and the longest name is 15 letters long.</p><p>to outline my initial approach to the problem of generating new names based on the training data is the following: we can deterministically figure out the probability of one letter coming after another by going through the entire dataset, keeping track of all the bigrams (two letter words combos such as &#8216;eg&#8217; or &#8216;va&#8217;) and then determine probabilities of each bigram occurring, and then using those probabilities make a guess at what letter is likely to come next given a letter, and use that to build up full names by continuously generating the next letter based on the previous. So we will only be looking one letter ahead for now.</p><p>if that&#8217;s a bit confusing dont worry, lets get into it step by step.</p><p>so firstly lets loop through all the words in the list of words, and for each word we use a clever method called zip we allows us to iterate through every bigram in all the names in the text file. additionally for every name we iterate through we add special characters to denote the start and end of each name. this will help us later on in figuring out the probabilities of a word starting/ending with a letter.</p><pre><code><code>b = {}
for w in words:
    chs = ['&lt;S&gt;'] + list(w) + ['&lt;E&gt;']
    for ch1, ch2 in zip(chs, chs[1:]):
        bigram = (ch1, ch2)
        b[bigram] = b.get(bigram, 0) + 1
        #print(ch1, ch2)
sorted(b.items(), key = lambda kv: -kv[1])

[(('n', '&lt;E&gt;'), 6763),
 (('a', '&lt;E&gt;'), 6640),
 (('a', 'n'), 5438),
 (('&lt;S&gt;', 'a'), 4410),
 (('e', '&lt;E&gt;'), 3983),
 (('a', 'r'), 3264),
 (('e', 'l'), 3248),
 (('r', 'i'), 3033),
 (('n', 'a'), 2977),
...]</code></code></pre><p>if you can&#8217;t decipher the output above, this is what is happening: we maintain a dictionary of all the bigrams and their frequencies then we break each word into it&#8217;s bigrams and increment it&#8217;s corresponding count in the map. after we&#8217;ve looped through all the words we sort the map, in this case the most common bigram is (&#8216;n&#8217;, &#8216;&lt;E&gt;&#8217;) indicating &#8216;n&#8217; followed by the name ending.</p><p>to make the code very clear the flow for the name &#8220;emma&#8221; would be processed like this: &#8220;emma&#8221; &#8594; &#8220;&lt;S&gt;emma&lt;E&gt;&#8221; &#8594; [(&#8220;&lt;S&gt;&#8221;, &#8220;e&#8221;), (&#8220;e&#8221;, &#8220;m&#8221;),&#8230;] and so on for all the bigrams in &#8220;emma&#8221;.</p><p>so this works but it&#8217;s not very efficient or extendable. ideally what we want is a 2D array/matrix that will store the counts for each possible bigram combo where we have &#8216;a&#8217; to &#8216;z&#8217; down the x-axis and &#8216;a&#8217; to &#8216;z&#8217; along the y-axis as well as a row and column for &#8216;.&#8217;. we would then easily be able to find the probably of &#8220;a&#8221; coming after &#8220;n&#8221; by finding &#8220;a&#8221; on the x-axis and &#8220;n&#8221; on the y-axis and lining them up. again if this doesn&#8217;t make sense, just scroll down a bit to see the final matrix we come up with. </p><p>first to make our lives easier lets start using PyTorch instead of default Python code. </p><p>another note: instead of keeping track of &lt;S&gt; and &lt;E&gt; for the start and end we will instead replace them both with a &#8220;.&#8221; - this will make our matrix much easier to read. a future optimisation would preferably use different tokens for start vs end but lets keep our lives simple for now.</p><p>we will import Torch and create a 27x27. that is 26 letters + the &#8220;.&#8221; to indicate either start/end.</p><pre><code><code>import torch
N = torch.zeros((27,27), dtype = torch.int32)</code></code></pre><p>this just create a 2D array with all counts starting at 0.</p><p>given that our matrix works in numbers(or indexes) and not letters we will need a method to convert letters to indices into the matrix. </p><pre><code><code>chars = sorted(list(set(''.join(words))))
stoi = {s:i+1 for i,s in enumerate(chars)}
stoi['.'] = 0
itos = {i:s for s,i in stoi.items()}

stoi
{'a': 1, 'b': 2, 'c': 3,'d': 4,'e': 5,'f': 6,'g': 7,'h': 8,'i': 9,'j': 10,'k': 11, 
'l': 12, 'm': 13,'n': 14,'o': 15,'p': 16,'q': 17,'r': 18,'s': 19,'t': 20, 'u': 21,
 'v': 22, 'w': 23, 'x': 24, 'y': 25, 'z': 26,'.': 0}</code></code></pre><p>stoi{} converts the letters into numbers and the itos{} converts the numbers back into their corresponding letters. above is what stoi map looks like.</p><p>next we can loop through all the bigrams again but this time we will be adding the counts to our 2D array/matrix.</p><pre><code>for w in words:
    chs = ['.'] + list(w) + ['.']
    for ch1, ch2 in zip(chs, chs[1:]):
        ix1 = stoi[ch1]
        ix2 = stoi[ch2]
        N[ix1, ix2] += 1</code></pre><p>once we have done that lets visualise our progress so far.</p><pre><code><code>import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(16,16))
plt.imshow(N, cmap='Blues')
for i in range(27):
    for j in range(27):
        chstr = itos[i] + itos[j]
        plt.text(j, i, chstr, ha="center", va="bottom", color='gray')
        plt.text(j, i, N[i,j].item(), ha="center", va="top", color='gray')
plt.axis('off')</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7JZN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7JZN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 424w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 848w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 1272w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7JZN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png" width="1118" height="1118" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1118,&quot;width&quot;:1118,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:639601,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/205766729?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7JZN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 424w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 848w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 1272w, https://substackcdn.com/image/fetch/$s_!7JZN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa42903f2-c36d-45a4-9809-e82fea93d10e_1118x1118.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>now we have a table with every bigram count from our training data. for example the entry &#8220;zh&#8221; = 43 tells us that the string &#8220;zh&#8221; occurs 43 times in all the names in the training data. also &#8220;.g&#8221; = 669 implies 669 names start with &#8216;g&#8217; and lastly &#8220;x.&#8221; = 164 implies that 164 names end with x.</p><p>so now lets take the second row for example, this is the row for &#8216;b&#8217;, so if we want to figure out the most likely next letter in the name given the current letter we are looking at is &#8216;b&#8217; this is the row that will help us do that. </p><p>we can sum the row&#8217;s counts for each entry in b&#8217;s row and then divide each entries count by the row&#8217;s sum to determine the probability of that specific letter coming after &#8216;b&#8217; in a name.</p><p>in other words we are normalising the row. the values in the row after dividing will now sum to 1.</p><p>lets do that for just row &#8216;b&#8217; first:</p><pre><code><code>N[2]
tensor([114, 321,  38,   1,  65, 655,   0,   0,  41, 217,   1,   0, 103,   0,
          4, 105,   0,   0, 842,   8,   2,  45,   0,   0,   0,  83,   0],
       dtype=torch.int32)
p = N[2].float()
p = p / p.sum()
p
tensor([0.0000, 0.1377, 0.0408, 0.0481, 0.0528, 0.0478, 0.0130, 0.0209, 0.0273,
        0.0184, 0.0756, 0.0925, 0.0491, 0.0792, 0.0358, 0.0123, 0.0161, 0.0029,
        0.0512, 0.0642, 0.0408, 0.0024, 0.0117, 0.0096, 0.0042, 0.0167, 0.0290])</code></code></pre><p>firstly we just grab the &#8216;b&#8217; row from the matrix, we then convert the values to floats so they can have decimal points. Once we have done that we can take each entry in the row and divide it by the sum of the row(normalise it). that gives us the <em>p</em> tensor. this contains the probabilities of each letter occurring next given we have a &#8216;b&#8217; right now as the current letter. example: p[4] gives us the probability that &#8216;c&#8217; comes after &#8216;b&#8217; based on the names we have seen in the training data set. 0.0481 implies there is about a 5% chance that &#8216;c&#8217; would be the next letter after &#8216;b&#8217;.</p><p>now given a single rows probabilities how do we choose the next letter based on those probabilities? this is where the multinomial comes in&#8230;</p><pre><code><code>p = tensor([0.0000, 0.1377, 0.0408, 0.0481, 0.0528, 0.0478, 0.0130, 0.0209, 0.0273,
        0.0184, 0.0756, 0.0925, 0.0491, 0.0792, 0.0358, 0.0123, 0.0161, 0.0029,
        0.0512, 0.0642, 0.0408, 0.0024, 0.0117, 0.0096, 0.0042, 0.0167, 0.0290])
from earlier^^^

ix = torch.multinomial(p, num_samples=1, replacement=True).item()
itos[ix]
'd'</code></code></pre><p>the above code uses the probabilities above to choose a number between 1 and 27, each with a probability of being chosen given above in the tensor p. the numbers it chooses correspond to the index of the letter it determines is likely to come next.</p><p>we then convert that chosen index into a string using the map we created earlier and the output is &#8216;d&#8217;. based on the probabilities, and relative frequency from our training data &#8216;d&#8217; had a 5% chance of being after &#8216;b&#8217;. if we kept sampling using the multinomial we would probably get a different letter each time, it does not pick the most likely next letter, it picks a letter based on it&#8217;s probability to be picked which is what is stored in tensor p.</p><p>to sample an entire word we would run the above code again and again. so we would generate &#8216;b&#8217; lets say as the first letter using the top row of our table above(since the top row corresponds to probabilities of words starting with that given letter) and then we would refer to the row corresponding to &#8216;b&#8217; to generate the next letter and so on until we generate a &#8216;.&#8217; which means the end of the name.</p><p>lets do this now - first lets turn the entire matrix we generated above into probabilities (in other words normalise every row) so we dont need to normalise each row every time we sample. you&#8217;ll observe now that each row in the matrix below sums to 1.</p><pre><code>P = N.float()
P /=P.sum(1, keepdims=True)</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K8E-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K8E-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 424w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 848w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 1272w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K8E-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png" width="1350" height="1334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1334,&quot;width&quot;:1350,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1200193,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/205766729?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K8E-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 424w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 848w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 1272w, https://substackcdn.com/image/fetch/$s_!K8E-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37467dea-6036-4887-bd80-2668763fe66b_1350x1334.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>now that we have our probability matrix we can start sampling from it. lets generate 5 names:</p><pre><code><code>for i in range(5):
    ix = 0
    out = []
    while True:
        p = P[ix]
        ix = torch.multinomial(p, num_samples=1, replacement=True).item()
        out.append(itos[ix])
        if ix == 0:
            break
    print(''.join(out))

cexze.
momasurailezitynn.
konimittain.
llayn.
ka.</code></code></pre><p>read through the code and convince yourself that this is using the probability matrix above to continuously generate letters based on the previous letter until we reach a &#8216;.&#8217; which means the word has ended. remember given any letter there is always a chance the next character is a &#8216;.&#8217; which implies the end of a name. refer to the first column of the above matrix for those probabilities.</p><p>fair enough that worked but did it really?</p><p>the names we generated still look pretty random, how can we prove that we did &#8220;train&#8221; this network on the 32,000 names in the text file.</p><p>well lets say we add 10000 to every probability in the P matrix above, in other words make every letter just as likely as the next for every letter, here is the output:</p><pre><code><code>for i in range(4):
    ix = 0
    out = []
    while True:
        p = P[ix] + 10000
        ix = torch.multinomial(p, num_samples=1, replacement=True).item()
        out.append(itos[ix])
        if ix == 0:
            break
    print(''.join(out))

zoglkurkicqzktyhwmvmzimjttainrlkfukzkktda.
sfcxvpubjtbhrmgotzx.
iczixqctvujkwptedogkkjemkmmsidguenkbvgynywftbspmhwcivgbvtahlvsu.
dsdxxblnwglhpyiw.</code></code></pre><p>as you can see the output is pretty garbage compared to what we had earlier. i call that progress!!!</p><p>and that as far as we are going to go today&#8230;</p><p>as a sneak peek of what we will do in part 2&#8230;we need a way of calculating how &#8220;good&#8221; the output is compared to what we would actually like it to output. we will be using something called the negative log likelihood for this. </p><p>this will help us adjust the weights and biases of our neural network. what we end up doing with the neural net is basically updating the matrix P&#8217;s probabilities to be as accurate as possible but instead of counting the frequencies of bigrams we feed in training data and adjusts parameters which adjusts the values stored in P.</p><p>for now dont worry about that, if you&#8217;re excited for part 2 the best thing you can do now is understand what we did today back to front, implement it yourself, try the exercise below. you can use your own .txt file of names or any training data. use your llm of choice to explain anything I missed or glossed over. get your code working like mine and I promise you will double your level of understanding. </p><p>next up &#8594; real neural networks!</p><p>an exercise for the readers at home, try to extend this program but instead of using bi-grams I want you to use tri-grams. In other words, find the next most likely letter based on the TWO preceding letters instead of just the previous letter.</p><p>references: </p><p><a href="https://www.youtube.com/watch?v=PaCmpygFfXo">https://www.youtube.com/watch?v=PaCmpygFfXo</a></p><p><a href="https://github.com/karpathy/makemore">https://github.com/karpathy/makemore</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Python vs Rust - Which one should you use?]]></title><description><![CDATA[Simplicity vs Speed]]></description><link>https://www.tomdev.blog/p/python-vs-rust-which-one-should-you</link><guid isPermaLink="false">https://www.tomdev.blog/p/python-vs-rust-which-one-should-you</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Sat, 13 Jun 2026 14:55:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-7Mv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe5fff54-446a-4ce9-8a75-ecf69a4985ec_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>firstly i dont claim to be a pro at Rust, i&#8217;ve actually only started learning it within the last couple of months since it&#8217;s been starting to take off <a href="https://aws.amazon.com/blogs/opensource/sustainability-with-rust/">inside of Amazon</a> as seen with projects like Fire Cracker which powers AWS Lambda for example. </p><p>today what i&#8217;d like to do since im assuming you are not already familiar with Rust is compare it to a language you are probably already familiar with - Python. </p><p>Python was created over 35 years ago by a dude called Guido Van Rossum, it only became popular much later on though since it launched around relatively the same time as Java which had a company called Sun marketing it with around a $500 million budget so it did not get it&#8217;s well deserved fame until much later&#8230;</p><p>Rust on the other hand was launched in 2009, which makes it relatively new compared to most popular programming languages around these days&#8230; but it&#8217;s first stable version only came out in 2015 with the help of the Mozilla Foundation funding it. the creator was a dude called Graydon Hoare. he created it as a personal project after he was frustrated with an elevator software that crashed. Rust, he said, was named after a fungi with the same name that is &#8220;over-engineered for survival&#8221;. and as you&#8217;ll see it&#8217;s quite a fitting name.</p><p>so since im sure you probably know a lot about Python let me just tell you a couple more facts about Rust since it&#8217;s pretty interesting&#8230; During the early years, the Rust compiler was written in 38,000 lines of OCaml code and was based on &#8220;mostly decades-old research&#8221;, read more about it <a href="https://en.wikipedia.org/wiki/Rust_(programming_language)">here</a>.</p><p>anyways so how do you actually write a rust program, and how does it differ from Python? lets begin&#8230;</p><p>so starting of with the most basic program here&#8217;s how it&#8217;s done in python:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;4e035472-f443-47ec-8269-e415ca3128d2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">print("hello world")</code></pre></div><p>and here&#8217;s the equivalent rust program:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a34913b2-ad9a-4d2e-b2f0-c851049147e1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">fn main() {
    println!("Hello, world!");
}</code></pre></div><p>unlike Python, every Rust program needs a main method which is where the program&#8217;s entry point is. it has to be called <em>main()</em> and does not have to return anything. </p><p>printing in Rust is pretty similar to python, instead of print we just use something called println! did you notice the exclamation mark? that&#8217;s not me shouting at Rust to print something, it&#8217;s what we call a <a href="https://www.reddit.com/r/rust/comments/a91gw0/what_is_a_macro/">macro</a> in Rust. the macro expands at compile time to accept the number of arguments given to it. for example we could called println! like we did earlier or we could call it like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;0b824652-3d2e-40df-81ae-70e928e84b05&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">fn main() {
    let name = "tom";
    println!("Hello, {}", name);
}</code></pre></div><p>which would expand <em>println</em> into a function at compile time that now accepts two arguments instead of one.</p><p>so now we can print text to the screen, how does defining variables differ between Python &amp; Rust? Python is pretty simple:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;3f8d74d8-db7f-4bea-a44b-a3af114c0107&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">name = "Tom"
age = 22</code></pre></div><p>it genuinely does not get easier than that, in Rust we are going to have to be a little bit more specific:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;bf15c07b-2f87-4bd6-8cfe-5dcac3525cb2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">let name = String::from("Tom");
let mut age: u16 = 22;</code></pre></div><p>there is much to unpack here but let&#8217;s go through it slowly.</p><p>luckily Rust is usually able to infer the type which helps but we are free to specify it like we have done for the age variable if we like, <em>u16</em> being an unsigned 16-bit integer in this case. the<em> mut </em>keyword is what tells Rust that we want this variable to be able to change in the future, otherwise we are not allowed to change it&#8217;s value throughout it&#8217;s lifetime, it&#8217;s a constant. This is what helps rust know when to free memory. </p><p>this might surprise you but there is no such thing as null or undefined in rust, it does not exist. in python it&#8217;s as simple as:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;b958bcee-02e6-4762-af23-6f61e089be15&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">name = None
print(name.upper())  # crashes at runtime</code></pre></div><p>but in Rust we have something called a <em>Option</em> which works like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;05b79efd-00c4-432e-8357-b2ca9282a881&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">let name: Option&lt;String&gt; = None;

match name {
    Some(n) =&gt; println!("{}", n.to_uppercase()),
    None =&gt; println!("no name!"),
}</code></pre></div><p>Optionals somewhat exist in JavaScript too but this is how they work in Rust. an <em>Optional</em> can either have a value of <em>Some()</em> or <em>None</em>. we are then able to use something like <em>match</em> which is a fancy if statement. if the value of <em>name </em>has a value, i.e its of type <em>Some(n)</em> then print it out uppercased, if its of value <em>None</em> then print out &#8220;no name&#8221;. <em>Some()</em> basically wraps the value contained within the Option if it exists. </p><p><em>null</em> <em>&amp; undefined</em> literally not existing in Rust prevents a whole class of null pointer exception bugs that are all too common in lower level languages where you need to manage memory yourself.</p><p>speaking of memory management how does it work in Rust vs Python? well in Python there is a garbage collector so that makes it pretty trivial. a garbage collector is the thing that runs in the background freeing up memory while your program runs, thats the same for most languages these days. Rust does something pretty unique. it does not have a garbage collector and at the same time does not expect you to free memory manually. </p><p>you might be thinking that that sounds impossible but something special about Rust which i&#8217;ll cover in a future article called the Borrow Checker allows Rust to guarantee that as soon as a variable goes out of scope it can safely drop it without your program encountering any errors.</p><p>I could go on and on about how great Rust is but I currently still default to Python when I need to get something up and running quickly. if you truly need the speed while still demanding memory safety then Rust is great if you are willing to sacrifice development speed since you really have to use your brain when writing Rust code, but it is definitely one of the most satisfying languages to code in that i&#8217;ve ever come across.</p><p>in summary, rust and python are pretty different beasts. python is easy to get started with, has a garbage collector handling memory for you, and lets you be loose with types and nulls &#8212; which is great until it isn&#8217;t. rust on the other hand gives you that same memory safety but without the garbage collector, the compiler handles it all at compile time. you also have no null, explicit types, and a main function as your entry point. it&#8217;s slower to write but faster to run and significantly harder to shoot yourself in the foot with. </p><p>I kept this short and sweet on purpose, just as a mild introduction to Rust since it&#8217;s still new, if you enjoyed it and want to hear more about Rust and my journey learning Rust then please let me know in the comments!</p><p></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.tomdev.blog/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[api gateway: the front door nobody explained to you]]></title><description><![CDATA[a semi-deep dive into aws api gateway and how it works]]></description><link>https://www.tomdev.blog/p/api-gateway</link><guid isPermaLink="false">https://www.tomdev.blog/p/api-gateway</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Wed, 03 Jun 2026 17:40:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-7Mv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe5fff54-446a-4ce9-8a75-ecf69a4985ec_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>okay so if you read my <a href="https://www.tomdev.blog/p/what-is-aws-lambda-and-how-does-it-work">lambda post</a> you might remember i kept mentioning this thing called api gateway and then told you basically nothing about it. so here we are. every <a href="https://aws.amazon.com/api-gateway/">api gateway</a> explainer opens with the exact same sentence, &#8220;amazon api gateway is a fully managed service that makes it easy to create, publish, maintain, monitor and secure apis at any scale&#8221;, and then i close the tab yet again because i still have no idea what it actually does. so im not doing that.</p><p>quick credentials so you dont think im making this up&#8230; <a href="https://www.linkedin.com/in/tom-de-villiers/">im a software engineer at AWS</a> and since im on a frontend team i theoretically should have at least an idea of what api gateway is, so heres what i do know.</p><p>in simple terms api gateway is the front door to your backend. its the bit that sits between the public internet and your actual code. every request from the outside world hits api gateway first, and api gateway goes &#8220;okay who is this, are they allowed in, are they sending me way too many requests, and where is this request even supposed to go&#8221; and only then does it hand the request off to whatever is doing the real work behind it.</p><p>normally if you wanted to expose your code to the internet you would have to build all of that yourself. youd write the code that checks if someone is logged in, the code that stops one guy from spamming you with 10,000 requests a second and taking the whole thing down, the code that checks the incoming data isnt complete garbage, the code that sends /users to one place and /orders to another, the logging, the api keys for your customers&#8230; that is a lot of code&#8230;and none of that is the actual thing you set out to build. its all just door management. api gateway is the door management. you point it at your backend, you tell it the rules, and it handles everything that happens before a request is allowed to reach your code.</p><p>so what actually happens when a request comes in. someone out on the internet sends a GET request to your-api.com/users. api gateway catches it at the matching route. first it checks the authorizer(more on that below), are you even allowed to do this? if not you get a 401 and you never reach the backend at all. then it checks throttling, are we over the rate limit? if yes you get a 429. then it can validate the request, did you actually send me the fields im expecting? if the request survives all of that, api gateway forwards it on to the integration (the thing that does the work - more on that shortly), waits for the response, potentially reshapes it, and sends it back to the caller. all the bouncer stuff happens before your code ever runs. as you can see it handles a ton of things that you&#8217;d have to manually do yourself.</p><p>the way you set it up is you define routes, which are literally just a method plus a path. GET /users, POST /users, GET /users/{id}, DELETE /orders/{id} and so on. each route points at an integration, which is just a fancy word for &#8220;the thing that handles this route&#8221; and the integration is usually one of three things. one, a lambda function, by far the most common, this is the lambda + api gateway combo i mentioned in the last post. two, some http endpoint you already have running, a server on ec2, a container, literally any url, and api gateway just forwards the request straight through to it. three, another AWS service directly with no code in between at all, you can have api gateway drop the request straight into an <a href="https://aws.amazon.com/sqs/">SQS queue</a> or write it into dynamodb without a single line of code.</p><p>okay so the whole point of this thing is the stuff it does for you that you would otherwise have to build by hand. auth is the big one, you attach an authorizer to a route and api gateway checks every single caller before letting them in, could be IAM, could be a <a href="https://aws.amazon.com/cognito/">cognito</a> user pool, or could be a <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html">lambda authorizer</a> where you write your own custom &#8220;is this token legit&#8221; logic. then theres throttling, you set a rate limit and api gateway enforces it so one client cant accidentally (or on purpose) take your backend down. theres api keys and usage plans, so if youre exposing an api to other people you can hand out keys and say this key gets 1000 requests a day and this one gets a million. and theres request validation, caching, CORS, custom domains and logging to cloudwatch, all of which you configure instead of code yourself.</p><p>now the one thing that confuses literally everyone. when you go to create an api, aws asks do you want a REST api or an HTTP api, and the names are useless because both of them are for building HTTP apis. heres the actual difference. <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html">REST apis</a> are the older, fully loaded option with every feature (api keys, usage plans, request validation, caching, etc). HTTP apis are the newer, stripped down option, fewer features but theyre cheaper and a bit faster. my rule of thumb is just start with an HTTP api and only reach for a REST api if you specifically need a feature it has. theres also WebSocket apis for real-time two-way stuff like chat apps but i havent got the chance to use those yet.</p><p>for the ceo&#8217;s reading this that care about pricing luckily the model is pretty simple, you basically pay per request. a few dollars per million requests, and HTTP apis are even cheaper than REST apis for the same traffic. theres also a free tier that covers a million calls a month for your first year which is way more than enough to mess around and build something real.</p><p>as usual there are a couple of limits it helps to know when youre weighing trade-offs. api gateway has roughly a 29 second timeout on integrations(oddly specific i know), so if your backend takes longer than that api gateway just gives up and returns an error, meaning its not built for long running jobs. it has a max payload size (10MB on REST apis) so its not for uploading huge files, for that youd hand out an s3 upload url instead(more on that in a future article). and theres a default account level rate limit (10,000 requests a second) which you can ask aws to raise if you ever get that big.(one day&#8230;)</p><p>honestly the fastest way to actually get this is to go build the tiny version. open the <a href="https://console.aws.amazon.com/apigateway">aws console</a>, make a lambda with the 3 line handler from <a href="https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html">my last post</a>, then create an HTTP api, add one route, point it at the lambda, and hit the url it gives you in your browser. then watch your own request travel internet &#8594; api gateway &#8594; lambda &#8594; back to your browser and thats genuinely it.<a href="https://www.youtube.com/watch?v=QqrGIGQ27qo">this</a> is a great tutorial if you get stuck.</p><p>anyway thats api gateway.it decides who gets in, how often, and where they go, and then it hands the request off to your real code. everything else like the authorizers, the throttling, REST vs HTTP, the pricing, is just detail layered on top of that one idea. if you remember nothing else from this remember &#8220;front door&#8221; and youre 90% of the way there.</p><p>if you learnt something subscribe! xD</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.tomdev.blog/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[What is AWS Lambda & How Does It Work?]]></title><description><![CDATA[The explanation I wish I had a few years ago]]></description><link>https://www.tomdev.blog/p/what-is-aws-lambda-and-how-does-it-work</link><guid isPermaLink="false">https://www.tomdev.blog/p/what-is-aws-lambda-and-how-does-it-work</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Tue, 26 May 2026 20:25:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-7Mv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe5fff54-446a-4ce9-8a75-ecf69a4985ec_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>okay so every <a href="https://aws.amazon.com/lambda/">AWS lambda</a> explainer ive ever read does the same thing, it opens with &#8220;lambda is a serverless compute service that lets you run code without provisioning servers&#8221; and then i close the tab because i still dont actually know what that means. so im not going to do that. im going to explain lambda the way i wish someone had explained it to me when i was new and confused, and by the end of this you will actually get it, not just be able to repeat the AWS website says.</p><p>just so you know i have some sort of credentials to speak about this&#8230; <a href="https://www.linkedin.com/in/tom-de-villiers/">im a software engineer at AWS</a> and have time and time again worked on lambdas so although i dont know everything here&#8217;s what i do know</p><p>in simple terms lambda says give me your source code. i&#8217;ll allocate a server and run it only when there is a request for it. i&#8217;ll take care of hardware, OS, security patches, availability, scaling, maintenance. i won't charge you when your code isn't running.</p><p>normally if you wanted to run code on the internet you would go <a href="https://www.ibm.com/think/topics/provisioning">rent a server</a>, install linux, install node or python or whatever, write a little web server that listens on a port, set up a process manager so it restarts when it crashes, get an ssl cert, set up logging, set up monitoring, and then your code finally runs. and that server is on 24/7 whether anyone is using it or not, and youre paying for it 24/7 whether anyone is using it or not. so in my case i&#8217;d be paying for an entire server to accommodate my 3 active users :(</p><p>lambda just removes all of that. you write a function. one function. it looks like this in python:</p><p><a href="https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html">`def handler(event, context): return {&#8221;hello&#8221;: &#8220;world&#8221;}`</a></p><p>that is genuinely all you have to write. you upload it, you tell aws &#8220;hey when something happens, run this code with this information&#8221;, and from that moment on aws is responsible for everything else. the server. the runtime. the scaling. the patching. the restart-when-it-crashes. all of it. you just own the function.</p><p>so the natural next question is what is the something that causes the function to run, and the answer is <a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-services.html#listing-of-services-and-links-to-more-information">basically anything</a>. someone hits an api url, run my function with the form data. a file gets uploaded to s3, run my function with the file. a message lands in a queue, run my function to process the message. its 6am every day, run my function. a database row changes, run my function to send the row to S3 for longterm storage. these are called triggers or event sources and there are like 200 of them and you can pick whichever one matches what youre trying to do.</p><p>okay so now that the triggered fired what happens before the code actually starts executing? lets see</p><p>an event comes in. the lambda looks around and goes &#8220;do i have a warm lambda environment ready for this function?&#8221; if yes, great, it shoves the event into that environment and your handler runs basically instantly, well technically in a couple of milliseconds. if no, lambda has to spin up a brand new tiny linux sandbox just for you, download your code into it, start the runtime (the python interpreter or the node process etc.), import all your dependencies, and then run your function. that whole spin-up is called a <a href="https://aws.amazon.com/blogs/compute/understanding-and-remediating-cold-starts-an-aws-lambda-perspective/">cold start</a> and depending on your language and how much code you&#8217;ve got it can be anywhere from 100ms to a couple of seconds.</p><p>after your function finishes lambda keeps it warm for a bit (usually somewhere between a few minutes and an hour) in case another event comes in soon. if another event does come in there is an environment ready to process the request. if not, the environment will eventually disappear.</p><p>okay so that covers what it is and how it runs. lets talk about what you actually use it for.</p><p>the most common use is sticking a lambda behind something called <a href="https://aws.amazon.com/api-gateway/">api gateway</a> and using the two together as a backend api. someone sends a POST request to your-api.com/users, api gateway catches it, invokes your lambda, your lambda does the work, returns a response, api gateway sends it back. pretty easy!</p><p>second big use case is file processing. user uploads an image to s3, s3 fires an event, lambda picks up the event which includes the image, resizes it into thumbnails, saves them back to s3. the upload itself is what triggered the work. this is usually configured pretty easily via the aws console or <a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html">CDK</a>. same with all the other integrations.</p><p>third is using lmabdas to connect services together. you have system A and system B and they need to talk to each other but they dont quite speak the same language. solution&#8230;write a lambda. it reads from one, transforms the data, writes to the other. from my experience at aws this is probably the most popular use case for lambdas.</p><p>fourth is scheduled jobs. <a href="https://cron-job.org/en/">cron</a> but in the cloud. some examples off the top of my head are every night at 3am clean up old records, every monday email a report, every 5 minutes check if the api is healthy. eventbridge fires the schedule, lambda runs the function, you go back to sleep. again these sound complex but connecting them is actually really simple.</p><p>i dont have a lot of experience with pricing since as a dev at AWS we don&#8217;t think a lot about pricing but you pay for two things. number of invocations and how long they run multiplied by how much memory you gave them. there is also a free tier gives you 1 million invocations and 400,000 gb-seconds per month for free. by gb-seconds its the amount of ram allocated to your lambda multiplied by how long the lambda ran.</p><p>there are a <a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html">couple of limits</a> that lambda has though which you honestly dont have to remember but it does help to know when you are discussing trade-offs etc.</p><p>it has a hard 15 minute max runtime. it has a stateless model, meaning anything you store in memory or on disk during one invocation is gone the next time, so if you need to remember things between requests you have to put that state somewhere external like dynamodb or s3 or a database. in other words if you create a variable during one invocation the next invocation wont be able to access it. it has cold starts, so if youre building something where every single millisecond of latency matters think about using provisioned concurrency which basically keeps a set amount of lambdas always pre-warmed.</p><p>okay so how do you actually try this. honestly the fastest path is just open the <a href="https://eu-north-1.signin.aws.amazon.com/oauth?client_id=arn%3Aaws%3Asignin%3A%3A%3Aconsole%2Fcanvas&amp;code_challenge=jEmJblK0uXWKWj4r1A-vJ2amd5Tdk4tRY_-QoLuPmkk&amp;code_challenge_method=SHA-256&amp;response_type=code&amp;redirect_uri=https%3A%2F%2Fconsole.aws.amazon.com%2Fconsole%2Fhome%3Fca-oauth-flow-id%3Dfe69%26hashArgs%3D%2523%26isauthcode%3Dtrue%26oauthStart%3D1779826643235%26state%3DhashArgsFromTB_eu-north-1_4ec2e677c4360f12">aws console</a>, search for lambda, hit &#8220;create function&#8221;, pick python or node, paste the 3 line handler i wrote earlier, hit test, watch it run, look at the logs in cloudwatch. the whole thing takes maybe 4 minutes and i think doing this once will teach you more than reading 10 more articles like this one. seriously. close this tab after you finish reading and just go click around. its free. use chatgpt if you get stuck.</p><p>anyway thats lambda. its just a function that runs when something happens and you pay only for the time it ran. everything else like the runtimes, the triggers, the scaling, the pricing, the cold starts is all just extra details which i honestly learnt a lot of when writing this blog haha. if you remember nothing else from this article remember that one sentence and youll be fine.</p><p>i want you to actually go build something with this btw, not just read about it, because lambda is one of those things that goes from &#8220;kinda confusing&#8221; to &#8220;oh thats it?&#8221; the second you deploy your first one. so go do it.</p><p>if you learnt something subscribe! xD</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.tomdev.blog/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[exactly how i'd escape tutorial hell if i had to do it again]]></title><description><![CDATA[step by step - full guide]]></description><link>https://www.tomdev.blog/p/exactly-how-id-escape-tutorial-hell</link><guid isPermaLink="false">https://www.tomdev.blog/p/exactly-how-id-escape-tutorial-hell</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Sat, 21 Feb 2026 08:28:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a80fdcb0-0d67-4899-b769-b5bdff055add_1063x505.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>watching &amp; following tutorials feels productive and that is the problem, at least it was for me.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VIfo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VIfo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 424w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 848w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 1272w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VIfo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png" width="1456" height="430" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:430,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:150988,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VIfo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 424w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 848w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 1272w, https://substackcdn.com/image/fetch/$s_!VIfo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bbcf12-d560-409f-a296-f75f9393502f_1611x476.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>i felt like every tutorial was going to be the last one and that once i&#8217;d completed this last 6 hour react tutorial i&#8217;d be able to build anything, obviously that was not the case.</p><p>i would grind through the tutorial, copying the code step by step, even typing along with the video to feel extra productive but alas the second i start building my own project and i ran into a bug i was stuck, straight to chatgpt for an answer (which is a whole other topic on its own btw). </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iP4W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iP4W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 424w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 848w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 1272w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iP4W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png" width="1456" height="361" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:361,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:101718,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iP4W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 424w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 848w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 1272w, https://substackcdn.com/image/fetch/$s_!iP4W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8b340ac-f5f8-441c-8041-5cef09478b55_1579x391.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>dont get me wrong though, this can be a great way to get the absolute basics of language or topic, a high level overview is always a great way to begin learning something new but if you really want to deeply understand something tutorials that guide you every step of the way are not the way i&#8217;d go.</p><p>so what now? no tutorials? yes that&#8217;s right. im going to limit you to at absolute maximum 2 tutorials when you are starting to learn something new, lets take react as an example. there are hundreds of javascript basics tutorials all over the internet, watch two of those and then come back here. no more than two, okay?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AIP5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AIP5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 424w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 848w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 1272w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AIP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png" width="1456" height="502" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:502,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57979,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AIP5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 424w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 848w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 1272w, https://substackcdn.com/image/fetch/$s_!AIP5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc4ad9a5-26ce-40f3-bf52-6a27850d1033_1492x514.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><br>now that we&#8217;ve got the basics out of the way, you have a high level overview of the language, we are still going with javascript, so you understand the basics of javascript and now you need to solidify this knowledge in your brain. in a past life i&#8217;d jump straight into another tutorial and follow it step by step and feel super productive, or so i thought&#8230;</p><p>the next step is something i learnt a while ago from another blog i read and its called question driven development, it sounds odd but give me a chance. what you&#8217;re going to do is think of a problem and then break it up into small, meaningful questions and then google (dont ask chatgpt) the answers to these questions to help you solve the overall problem. if you&#8217;re confused dont worry let&#8217;s do an example.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E3_K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E3_K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 424w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 848w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 1272w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E3_K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png" width="1456" height="256" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:256,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:93254,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E3_K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 424w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 848w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 1272w, https://substackcdn.com/image/fetch/$s_!E3_K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55159429-c3c8-4334-ada3-551ca7057bb6_1863x327.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>you google &#8216;beginner programming problems javascript&#8217; and tons of lists of projects and problems come up, to start off with try to keep the number of unique lines of code written to about 100 or so. now after scrolling through websites and problems i find one called &#8216;palindrome checker&#8217; for example, so now i&#8217;ll break this problem into smaller questions that will help me solve this problem.</p><p>if you&#8217;re very new your questions could be &#8216;how do i run javascript on my laptop&#8217; or &#8216;which is the best ide for javascript&#8217;, but once you&#8217;ve done this a few times, and i recommend doing it with at least 50 different problems if you&#8217;re serious about learning, then you could start asking questions like &#8216;how do i read in a string in javascript&#8217; and &#8216;how do i store a string in an array in javascript&#8217;, these questions are small parts of the full problem that will eventually lead you to an working solution for the &#8216;palindrome checker&#8217; problem. </p><p>something very important that i strongly believe is at the start of learning anything new chatgpt is not your friend, a large amount of the learning process occurs when you&#8217;re brain has to piece bits of information together to make it make sense to you since stackoverflow answers are not directly related to your problem.</p><p>the problem with chatgpt is it does all the hard work for you of simplifying information down to easy to understand, digestible parts. this is bad. its fine when you&#8217;ve forgotten how to do something but not when you&#8217;re trying to learn it and engrain it into your mind. this deserves a whole new blog in itself.</p><p>now that you&#8217;ve solved 50 small problems, its time to move onto the big leagues&#8230; i want you to go find 3-4 already existing websites that you think are cool and clone them. yes you heard that correctly, the nice thing about cloning already existing websites is that it leaves very little room for procrastination. no need to think of a good idea or design the UI from scratch. lets do another example.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YfGo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YfGo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 424w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 848w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 1272w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YfGo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png" width="1456" height="259" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:259,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94326,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YfGo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 424w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 848w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 1272w, https://substackcdn.com/image/fetch/$s_!YfGo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb27b7a38-2bc1-4a83-a7d3-f15949895af3_1835x327.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>say i decide i want to clone the youtube homepage, what i&#8217;ll start doing is coming up with a plan and making use of question driven developer aka QDD again. i&#8217;m sure we are all familiar with youtube&#8217;s UI. i&#8217;ll start breaking it down into small parts.</p><p>so for starters there is the search bar, using QDD i&#8217;ll ask questions like &#8216;how do i create a search bar using html &amp; css&#8217; and then &#8216;how do i make a search bar functional using javascript&#8217; etc. and then ill continue to do this for each part of the homepage until i&#8217;ve got a solid plan, then ill just get to work. googling, implementing, fixing bugs, googling more, implementing more, and googling even more. no chatgpt.</p><p>once you&#8217;ve done this a couple times and i officially release you from your tutorial hell rehab, you can start building some more original projects from scratch.</p><p>but lets face it you are always going to need tutorials so the best way is to build a healthy relationship with them instead of becoming dependent on them.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vJHb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vJHb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 424w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 848w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 1272w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vJHb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png" width="728" height="345.8513640639699" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:505,&quot;width&quot;:1063,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:42403,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.tomdev.blog/i/188692158?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vJHb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 424w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 848w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 1272w, https://substackcdn.com/image/fetch/$s_!vJHb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef7acc04-8d66-43c1-a7c1-03e26e41050b_1063x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>generally what i like to do is make sure i&#8217;m always learning and then building right after, lets say im watching a video on how to build a navbar with react, right afterwards ill go build 3 very average navbars just to solidify it in my mind.</p><p>learn and then build. simple. if you learnt something subscribe!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.tomdev.blog/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[exactly how i'd get my first software engineering internship if i had to start from scratch]]></title><description><![CDATA[the zero bullshit guide - no gatekeeping.]]></description><link>https://www.tomdev.blog/p/exactly-how-id-get-my-first-software</link><guid isPermaLink="false">https://www.tomdev.blog/p/exactly-how-id-get-my-first-software</guid><dc:creator><![CDATA[Tom De Villiers]]></dc:creator><pubDate>Wed, 18 Feb 2026 18:07:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-7Mv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe5fff54-446a-4ce9-8a75-ecf69a4985ec_500x500.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>honestly there was 2 ways i could approach this, option 1 would&#8217;ve been to just give super generic advice that seems like its helpful but when you really start trying to apply it it&#8217;s not and option 2, what i have decided to go with is to give incredibly practical, semi-unethical advice on how&#8217;d id go about getting my first software engineering internship if i had to start from scratch</p><p>firstly, just some of my credentials, im currently a software engineer at amazon web services, my team is responsible for this: <a href="https://health.aws.amazon.com/health/status">https://health.aws.amazon.com/health/status</a> and i interned at amazon last year where i worked on the algorithms behind amazon&#8217;s insane customer support experience, routing cases to the correct agent&#8217;s etc.</p><p>so since i said we are starting from scratch that is exactly where im going to start. i&#8217;m going to do this from my POV but obviously apply this to your own life.</p><p>since i&#8217;ve currently got absolutely nothing on my CV there are 2 places i&#8217;m going to start. firstly i&#8217;m going to start working on projects, and yes in 2026 i understand AI has increased the expectations of what a side project looks like but what i&#8217;ve found really impresses employers is having a project that is 1) deployed - meaning it&#8217;s live on a domain &amp; people can access it. even if absolutely nobody ever accesses it that does not matter. what matters is that for some reason when an employer can actually go to your site and use what you built it tends to get them all excited. 2) if i can manage to get a couple of people using it then that&#8217;s just extra points.</p><p>so by now i&#8217;ve worked on a couple projects, i&#8217;ve built a custom version of chess which im going to call obstacle chess where you are allowed to place bombs, walls and trap doors throughout the match, this has been deployed and can be accessed. i&#8217;ve used my creativity to show that i&#8217;m not just re-creating what can easily be found on google but adding my own spin to it.</p><p>now what i&#8217;m going to do is start looking for tangential jobs that don&#8217;t require software engineering experience, i&#8217;ve decided to start tutoring maths &amp; computer science to people in the year below me in university, initially i&#8217;ll offer to do it for free but then eventually i&#8217;ll start charging. this will look great on my CV.</p><p>something else i&#8217;m going to start doing is contacting every single person i know and asking them if they&#8217;d like me to re-do their website for free, i&#8217;m going to ask my uncle since he owns a construction business and his website could use some work, but i could genuinely ask anybody. great, I can now add &#8220;Junior Web Developer - Scribante Construction&#8221; to my CV, wow!</p><p>now that i&#8217;ve got a basic CV together, i&#8217;ve got 2 different work experiences listed, ive got a couple projects, i&#8217;ve got the degree im currently studying and all the technologies &amp; languages i know this is when i&#8217;ll start personally contacting small companies in my area, make sure you stay very local for now, asking if they&#8217;re consider hiring you to help with their IT, bonus points if there&#8217;s an software company near you. try to find the email&#8217;s of the higher up&#8217;s and send them an email directly with a short message and attach your CV.</p><p>realistically nobody&#8217;s first internship is that enjoyable, i know mine wasn&#8217;t, but its a stepping stone to getting into the big leagues.</p><p>once i&#8217;ve worked at around 2-3 local business during the holidays, potentially doing pretty lame, probably useless, but very valuable for my CV work this is when you have a good enough CV for it to stop being the bottle neck.</p><p>if you can check everything off this check list i&#8217;d say you&#8217;re good to start applying to proper software engineering internship roles.</p><ul><li><p>2-3 tangential roles related to software engineering at local/no-name companies</p></li><li><p>3-4 projects that are deployed (or were at a point)</p></li></ul><p>now the next step would be to start applying for roles and getting interviews.</p><p>genuinely i am going to just start applying everywhere, looking through linkedin, going to the career fair and socialising with recruiters, straight up just googling or asking chatgpt to find you roles to apply for. dont feel scared to apply for roles you think you aren&#8217;t cut out for, let them reject you instead of you rejecting yourself.</p><p>patience is definitely key here but throughout this process of applying for roles i&#8217;d be preparing for the interview when i eventually get one.</p><p>what that looks like for me is first following this blog post to the tee: </p><p><a href="https://interviewguide.dev/">https://interviewguide.dev/</a></p><p>not even going to lie and say i can explain how to interview better than the guide above can, go give it a read after this, it helped me so much. so much value in one blog post.</p><p>another great resource for getting cracked at leetcode is </p><p><a href="https://neetcode.io/">https://neetcode.io/</a></p><p>i paid for the pro-subscription and i was on the fence initially but that investment has produced 100x returns since it helped me eventually secure a full-time role at amazon.</p><p>once you&#8217;ve smashed the interview and secured the role you&#8217;ve basically got your foot in the door.</p><p>building up your CV and experience is a long process but one that will pay you back 100s of times over in the future. you are front-loading the effort now in order to reap the benefits later in life.</p><p>i hope this guide was semi-helpful and a break from the generic bullshit you normally see online from people trying to explain how to get your first internship. i am not gate-keeping any information. i want you to succeed as bad as i want myself to succeed</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.tomdev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">thanks for making it this far, this was my first ever post so if you&#8217;d like to motivate me to write more please consider subscribing xD</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>