<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peakblick</title>
    <description>The latest articles on DEV Community by Peakblick (@peakblick).</description>
    <link>https://dev.to/peakblick</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136277%2F88df5c76-2bff-4005-8bed-97466825e364.png</url>
      <title>DEV Community: Peakblick</title>
      <link>https://dev.to/peakblick</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/peakblick"/>
    <language>en</language>
    <item>
      <title>Django select_related vs prefetch_related (With Examples)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Tue, 13 Oct 2026 06:36:40 +0000</pubDate>
      <link>https://dev.to/peakblick/django-selectrelated-vs-prefetchrelated-with-examples-3aih</link>
      <guid>https://dev.to/peakblick/django-selectrelated-vs-prefetchrelated-with-examples-3aih</guid>
      <description>&lt;p&gt;Both exist to fix the same thing — the N+1 query problem — but they work on different kinds of relationships. The whole decision comes down to one question: is the relationship &lt;strong&gt;single-valued&lt;/strong&gt; (a ForeignKey) or &lt;strong&gt;multi-valued&lt;/strong&gt; (a ManyToMany)?&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem they solve: N+1 queries
&lt;/h2&gt;

&lt;p&gt;Say each &lt;code&gt;Book&lt;/code&gt; has a ForeignKey to &lt;code&gt;Author&lt;/code&gt;. This innocent loop quietly runs one query for the books, then one more &lt;em&gt;per book&lt;/em&gt; to fetch its author:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# a new DB query every iteration
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;100 books → 101 queries. That's the N+1 problem, and it's the single most common Django performance bug. &lt;code&gt;select_related&lt;/code&gt; and &lt;code&gt;prefetch_related&lt;/code&gt; both collapse those extra queries — they just do it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  select_related — for ForeignKey and OneToOne
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;select_related&lt;/code&gt; follows a &lt;strong&gt;single-valued&lt;/strong&gt; relationship (ForeignKey, OneToOne) by doing a SQL &lt;code&gt;JOIN&lt;/code&gt; and pulling the related object in the &lt;em&gt;same&lt;/em&gt; query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# no extra queries — author came via the JOIN
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;101 queries become &lt;strong&gt;1&lt;/strong&gt;. Because it's a database join, it only works when each row has exactly one related row — that's why it's for ForeignKey and OneToOne, the "forward" single object.&lt;/p&gt;

&lt;h2&gt;
  
  
  prefetch_related — for ManyToMany and reverse ForeignKey
&lt;/h2&gt;

&lt;p&gt;A JOIN can't efficiently fetch a &lt;strong&gt;multi-valued&lt;/strong&gt; relationship (many rows per object), so &lt;code&gt;prefetch_related&lt;/code&gt; takes a different approach: it runs a &lt;em&gt;second&lt;/em&gt; query for all the related objects, then stitches them together in Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;authors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;books&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;   &lt;span class="c1"&gt;# no extra query per author
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;strong&gt;2&lt;/strong&gt; queries total (one for authors, one for all their books) instead of one-per-author. Use it for ManyToMany fields and for reverse ForeignKey access (the "many" side).&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;This is the whole thing to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select_related   -&amp;gt; single-valued (ForeignKey, OneToOne)   -&amp;gt; SQL JOIN, 1 query
prefetch_related -&amp;gt; multi-valued  (ManyToMany, reverse FK) -&amp;gt; separate query, joined in Python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put another way: if you'd follow the relationship to &lt;em&gt;one&lt;/em&gt; object (&lt;code&gt;book.author&lt;/code&gt;), use &lt;code&gt;select_related&lt;/code&gt;. If you'd follow it to a &lt;em&gt;set&lt;/em&gt; of objects (&lt;code&gt;author.books.all()&lt;/code&gt;), use &lt;code&gt;prefetch_related&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining them
&lt;/h2&gt;

&lt;p&gt;Real queries often need both, and they chain cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# each book's author (FK) AND each book's tags (M2M), minimal queries
&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also span relationships with &lt;code&gt;__&lt;/code&gt; — &lt;code&gt;select_related('author__publisher')&lt;/code&gt; joins two levels of ForeignKey in one query, and &lt;code&gt;prefetch_related('author__books')&lt;/code&gt; prefetches across them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wrong tool, no error:&lt;/strong&gt; using &lt;code&gt;prefetch_related&lt;/code&gt; on a ForeignKey works but runs an extra query you didn't need — &lt;code&gt;select_related&lt;/code&gt; is strictly better there. Using &lt;code&gt;select_related&lt;/code&gt; on a ManyToMany raises an error, because you can't JOIN a multi-valued relation into one row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering after prefetch re-queries:&lt;/strong&gt; calling &lt;code&gt;.filter()&lt;/code&gt; on a prefetched set hits the DB again. To filter the prefetched data, use a &lt;code&gt;Prefetch&lt;/code&gt; object with a queryset instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only prefetch what you use:&lt;/strong&gt; prefetching a relation you never touch just wastes a query and memory.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The N+1 problem and these two methods are a staple of Django interviews — see the full &lt;a href="https://peakblick.com/blog/django-interview-questions.html" rel="noopener noreferrer"&gt;Django interview questions&lt;/a&gt; and the &lt;a href="https://peakblick.com/blog/sql-interview-questions.html" rel="noopener noreferrer"&gt;SQL questions&lt;/a&gt; on joins and query performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prepping for a Django interview?&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article11" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; runs mock interviews on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real thing. Free to try, no card.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Nested Loops in a Python List Comprehension</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Mon, 12 Oct 2026 20:40:49 +0000</pubDate>
      <link>https://dev.to/peakblick/nested-loops-in-a-python-list-comprehension-1614</link>
      <guid>https://dev.to/peakblick/nested-loops-in-a-python-list-comprehension-1614</guid>
      <description>&lt;p&gt;You can put more than one &lt;code&gt;for&lt;/code&gt; in a list comprehension to get the effect of nested loops — but the order of those &lt;code&gt;for&lt;/code&gt; clauses trips almost everyone up the first time. Here's how it actually works, with the one rule that makes it click.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea: same order as nested loops
&lt;/h2&gt;

&lt;p&gt;A comprehension with two &lt;code&gt;for&lt;/code&gt; clauses behaves exactly like two nested &lt;code&gt;for&lt;/code&gt; loops, and — this is the whole trick — &lt;strong&gt;the clauses read left to right in the same order you'd write the loops&lt;/strong&gt;: outer loop first, inner loop second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="c1"&gt;# [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line is identical to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;        &lt;span class="c1"&gt;# outer  -&amp;gt; first for-clause
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;    &lt;span class="c1"&gt;# inner  -&amp;gt; second for-clause
&lt;/span&gt;        &lt;span class="n"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the mental model is simple: copy the loops top to bottom, then the expression (&lt;code&gt;(x, y)&lt;/code&gt;) goes at the front. The inner loop runs fully for each step of the outer one, just like real nested loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one mistake everyone makes: the order
&lt;/h2&gt;

&lt;p&gt;The instinct is to write the clauses "inside out," but Python reads them &lt;strong&gt;outside in&lt;/strong&gt;. Each clause can only use variables defined by a clause to its &lt;em&gt;left&lt;/em&gt;. So this fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WRONG — 'row' is used before it's defined
&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# NameError: name 'row' is not defined
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to put the clauses in loop order — outer (&lt;code&gt;row&lt;/code&gt;) first, then inner (&lt;code&gt;n&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# RIGHT
&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you ever get a &lt;code&gt;NameError&lt;/code&gt; in a comprehension, it's almost always this: a later variable used in an earlier clause. Reorder them the way you'd nest the loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flattening a list of lists
&lt;/h2&gt;

&lt;p&gt;That last example is the most common real use of nested loops in a comprehension — turning a list of lists into one flat list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;flat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# [1, 2, 3, 4, 5, 6, 7, 8, 9]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it as "for each &lt;code&gt;row&lt;/code&gt; in the matrix, for each &lt;code&gt;n&lt;/code&gt; in that row, take &lt;code&gt;n&lt;/code&gt;." Outer first, inner second — same rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested comprehensions (a list of lists)
&lt;/h2&gt;

&lt;p&gt;This is a different thing, and it's worth not confusing the two. Nested &lt;code&gt;for&lt;/code&gt; clauses &lt;em&gt;flatten&lt;/em&gt;; a comprehension &lt;em&gt;inside&lt;/em&gt; another comprehension &lt;em&gt;builds structure&lt;/em&gt; — a list of lists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# build a 2x3 grid
&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="c1"&gt;# [[0, 1, 2], [0, 1, 2]]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the inner &lt;code&gt;[col for col in range(3)]&lt;/code&gt; produces one row, and the outer comprehension repeats it. A classic use is transposing a matrix (swapping rows and columns):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;transposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))]&lt;/span&gt;
&lt;span class="c1"&gt;# [[1, 4], [2, 5], [3, 6]]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule of thumb: multiple &lt;code&gt;for&lt;/code&gt; clauses in &lt;em&gt;one&lt;/em&gt; comprehension → a flat list; a comprehension nested in brackets → a list of lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a condition
&lt;/h2&gt;

&lt;p&gt;You can filter with &lt;code&gt;if&lt;/code&gt; after the &lt;code&gt;for&lt;/code&gt; clauses, and the condition can use any variable defined to its left:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# all pairs where the two numbers differ
&lt;/span&gt;&lt;span class="n"&gt;pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# [(0,1), (0,2), (1,0), (1,2), (2,0), (2,1)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple conditions just chain: &lt;code&gt;... if x != y if x + y &amp;gt; 1&lt;/code&gt; keeps only pairs that pass both.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stop and just write the loop
&lt;/h2&gt;

&lt;p&gt;Comprehensions are great until they aren't readable. One or two &lt;code&gt;for&lt;/code&gt; clauses to flatten or pair up values is clean Python. Three clauses, or a nested comprehension with conditions inside conditions, is usually the moment to write the plain nested loops instead — they're easier to read, debug, and change. The goal is clarity, not fitting everything on one line.&lt;/p&gt;

&lt;p&gt;Quick reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# nested loops, flat result
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# with a filter
&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# nested comprehension -&amp;gt; list of lists
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Nested comprehensions and the &lt;code&gt;for&lt;/code&gt;-clause order come up constantly in Python interviews — see the &lt;a href="https://peakblick.com/blog/python-interview-questions.html" rel="noopener noreferrer"&gt;Python interview questions&lt;/a&gt; for the data-model ideas behind them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prepping for a Python interview?&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article10" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; runs mock interviews on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real thing. Free to try, no card.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Sat, 10 Oct 2026 09:53:32 +0000</pubDate>
      <link>https://dev.to/peakblick/python-interview-questions-with-model-answers-486f</link>
      <guid>https://dev.to/peakblick/python-interview-questions-with-model-answers-486f</guid>
      <description>&lt;p&gt;These are the core-language Python questions that actually come up — grouped by topic, each with a short model answer and a note on what the interviewer is really checking. Python questions reward understanding the data model: most "gotchas" are the same few ideas (mutability, references, evaluation time) wearing different hats.&lt;/p&gt;

&lt;p&gt;The tell on Python questions is whether you can explain &lt;em&gt;why&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt;. "A tuple is immutable" is recall; "a tuple is immutable, which is why it can be a dict key and a list can't" is understanding. For each answer below, notice the pattern: name the mechanism, give the one consequence that matters, then stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data model
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between mutable and immutable types?
&lt;/h3&gt;

&lt;p&gt;A mutable object can be changed in place after creation; an immutable one can't. Lists, dicts and sets are mutable; ints, floats, strings, tuples and frozensets are immutable. It matters for three everyday reasons: only immutable objects can be dictionary keys or set members (they need a stable hash), immutable objects are safe to share without worrying someone mutates them under you, and "modifying" a string actually creates a new one. Most Python surprises trace back to this one distinction.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you connect mutability to hashing, sharing, and the gotchas below.&lt;/p&gt;

&lt;h3&gt;
  
  
  List vs tuple — when would you use each?
&lt;/h3&gt;

&lt;p&gt;A list is a mutable, ordered collection you grow and change; a tuple is an immutable, ordered collection you fix once. Use a list for a homogeneous sequence you'll modify (a queue of tasks); use a tuple for a fixed record whose shape has meaning (a coordinate &lt;code&gt;(x, y)&lt;/code&gt;, a row returned from a query). The practical payoffs of the tuple: it can be a dict key, it signals "this won't change," and it's slightly lighter.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you pick by mutability and intent, not just "tuple is a faster list."&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;is&lt;/code&gt; vs &lt;code&gt;==&lt;/code&gt; — what's the difference?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;==&lt;/code&gt; checks &lt;em&gt;equality&lt;/em&gt; (do these have the same value?); &lt;code&gt;is&lt;/code&gt; checks &lt;em&gt;identity&lt;/em&gt; (are these the exact same object in memory?). You almost always want &lt;code&gt;==&lt;/code&gt;. The one firm rule: compare to &lt;code&gt;None&lt;/code&gt; with &lt;code&gt;is&lt;/code&gt; (&lt;code&gt;if x is None&lt;/code&gt;), because &lt;code&gt;None&lt;/code&gt; is a singleton. A classic trap: small integers and short strings are cached, so &lt;code&gt;is&lt;/code&gt; sometimes &lt;em&gt;looks&lt;/em&gt; like it works for values — relying on that is a bug waiting to happen.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know identity vs equality, and the &lt;code&gt;is None&lt;/code&gt; convention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shallow copy vs deep copy?
&lt;/h3&gt;

&lt;p&gt;A shallow copy duplicates the outer object but shares the nested objects inside it; a deep copy recursively duplicates everything. So if you shallow-copy a list of lists and mutate an inner list, the change shows up in both copies — because they point at the same inner list. Use &lt;code&gt;copy.deepcopy()&lt;/code&gt; when you need true independence, and know it's slower. This is just the mutability rule again: copying the container doesn't copy what it references.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand references, and when a shallow copy quietly bites you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What do &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; do?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;*args&lt;/code&gt; collects extra positional arguments into a tuple; &lt;code&gt;**kwargs&lt;/code&gt; collects extra keyword arguments into a dict. They let a function accept any number of arguments, and — used at the call site — unpack a sequence or dict back into arguments (&lt;code&gt;f(*my_list)&lt;/code&gt;, &lt;code&gt;f(**my_dict)&lt;/code&gt;). The everyday use is writing wrappers and decorators that pass arguments through without knowing them: &lt;code&gt;def wrapper(*args, **kwargs): return func(*args, **kwargs)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you can write flexible, pass-through function signatures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a decorator?
&lt;/h3&gt;

&lt;p&gt;A decorator is a function that takes another function and returns a new one that wraps it — a clean way to add behaviour (logging, timing, caching, access checks) around a function without touching its body. &lt;code&gt;@decorator&lt;/code&gt; above a definition is just sugar for &lt;code&gt;func = decorator(func)&lt;/code&gt;. The reason they feel magical and then obvious: functions in Python are ordinary objects you can pass around and return, so wrapping one is nothing special.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you grasp functions-as-objects, which is the whole idea behind decorators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is a mutable default argument a bug?
&lt;/h3&gt;

&lt;p&gt;Because default arguments are evaluated &lt;strong&gt;once&lt;/strong&gt;, when the function is defined — not on each call. So &lt;code&gt;def add(x, items=[])&lt;/code&gt; shares one list across every call that uses the default, and it accumulates between calls, which is almost never what you want. The fix is the standard idiom: default to &lt;code&gt;None&lt;/code&gt; and create the list inside — &lt;code&gt;def add(x, items=None): items = items or []&lt;/code&gt;. It's the single most common Python gotcha in interviews.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know default arguments are evaluated at definition time, not call time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterators &amp;amp; generators
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a generator, and why use one?
&lt;/h3&gt;

&lt;p&gt;A generator is a function that uses &lt;code&gt;yield&lt;/code&gt; to produce values lazily, one at a time, instead of building and returning a whole list. Each &lt;code&gt;yield&lt;/code&gt; hands back a value and pauses, resuming where it left off on the next request. The win is memory: you can iterate over a billion-line file or an infinite sequence without ever holding it all in RAM. Reach for one whenever you're processing a large or streaming sequence and don't need every item at once.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you see generators as a memory optimisation for sequences, not just "fancy loops."&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between an iterable and an iterator?
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;iterable&lt;/strong&gt; is anything you can loop over — it can produce an iterator (a list, a string, a dict). An &lt;strong&gt;iterator&lt;/strong&gt; is the object that actually does the walking: it remembers its position and hands you the next item via &lt;code&gt;next()&lt;/code&gt;, raising &lt;code&gt;StopIteration&lt;/code&gt; when it's done. A &lt;code&gt;for&lt;/code&gt; loop quietly calls &lt;code&gt;iter()&lt;/code&gt; on the iterable to get an iterator, then &lt;code&gt;next()&lt;/code&gt; until it's exhausted. A list is iterable but not its own iterator; a generator is both.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know the protocol a &lt;code&gt;for&lt;/code&gt; loop actually runs under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  List comprehension vs generator expression?
&lt;/h3&gt;

&lt;p&gt;Same syntax, different brackets, different behaviour. &lt;code&gt;[x*2 for x in data]&lt;/code&gt; builds the whole list in memory immediately. &lt;code&gt;(x*2 for x in data)&lt;/code&gt; creates a generator that produces values lazily, one at a time. Use the list comprehension when you need the full list (you'll index it, reuse it, or it's small); use the generator expression when you're just going to iterate once over something large — it keeps memory flat.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you choose eager vs lazy evaluation on purpose, by size and reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency &amp;amp; resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the GIL, and what does it mean for threads?
&lt;/h3&gt;

&lt;p&gt;The Global Interpreter Lock is a mutex in CPython that lets only one thread execute Python bytecode at a time. The consequence: threads &lt;strong&gt;don't&lt;/strong&gt; give you true parallelism for CPU-bound work — two threads crunching numbers run effectively one at a time. But threads still help for &lt;strong&gt;I/O-bound&lt;/strong&gt; work (network, disk), because the GIL is released while waiting. So the rule is: threads for I/O-bound, &lt;code&gt;multiprocessing&lt;/code&gt; (separate processes, separate GILs) or native extensions for CPU-bound.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know the GIL's real-world rule — threads for I/O, processes for CPU.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a context manager, and when would you write one?
&lt;/h3&gt;

&lt;p&gt;A context manager is the object behind the &lt;code&gt;with&lt;/code&gt; statement: it runs setup on entry and — crucially — guaranteed cleanup on exit, even if an exception is raised. &lt;code&gt;with open(f) as file:&lt;/code&gt; closes the file no matter what. You write your own (with &lt;code&gt;__enter__&lt;/code&gt;/&lt;code&gt;__exit__&lt;/code&gt;, or the &lt;code&gt;@contextmanager&lt;/code&gt; decorator) whenever you have a resource that must be released — a lock, a database connection, a temporary change to state — and you don't want to rely on remembering to clean it up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you reach for &lt;code&gt;with&lt;/code&gt; for guaranteed cleanup, not manual try/finally everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Python manage memory?
&lt;/h3&gt;

&lt;p&gt;Primarily with &lt;strong&gt;reference counting&lt;/strong&gt;: every object tracks how many references point to it, and it's freed the instant that count hits zero. On top of that, a cyclic garbage collector handles the case reference counting can't — objects that reference each other in a cycle, which would otherwise never reach zero. You rarely manage memory by hand; the thing worth knowing is that holding a reference (in a long-lived list, a cache, a closure) is what keeps an object alive.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand reference counting and what actually causes a "leak" in Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;Almost every Python question above reduces to one of a few core ideas — mutability, references, when code is evaluated. The strongest candidates don't memorise each gotcha; they explain it from the principle: "default arguments evaluate once at definition, so a mutable default is shared — that's the same reason this bug and that one happen." Showing the underlying model beats reciting a list of tricks.&lt;/p&gt;

&lt;p&gt;Two habits that quietly raise your score: &lt;strong&gt;give the consequence, not just the definition&lt;/strong&gt; (a tuple is immutable → so it can be a dict key), and &lt;strong&gt;mention the standard idiom&lt;/strong&gt; where there is one (&lt;code&gt;is None&lt;/code&gt;, &lt;code&gt;items=None&lt;/code&gt; defaults, &lt;code&gt;with&lt;/code&gt; for cleanup) — it signals you've written real Python. Prepping the whole stack? Pair this with the &lt;a href="https://peakblick.com/blog/python-backend-interview-questions.html" rel="noopener noreferrer"&gt;Python backend&lt;/a&gt;, &lt;a href="https://peakblick.com/blog/sql-interview-questions.html" rel="noopener noreferrer"&gt;SQL&lt;/a&gt;, and &lt;a href="https://peakblick.com/blog/system-design-interview-questions.html" rel="noopener noreferrer"&gt;system design&lt;/a&gt; questions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Rehearse these out loud, scored.&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article9" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; asks you Python questions like these on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real interview. Free to try, no card.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>interview</category>
      <category>career</category>
    </item>
    <item>
      <title>REST API Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:21:18 +0000</pubDate>
      <link>https://dev.to/peakblick/rest-api-interview-questions-with-model-answers-2alh</link>
      <guid>https://dev.to/peakblick/rest-api-interview-questions-with-model-answers-2alh</guid>
      <description>&lt;p&gt;These are the REST API questions that actually come up in backend and full-stack interviews — grouped by topic, each with a short model answer and a note on what the interviewer is really checking. API questions reward clarity: they tell an interviewer whether you've designed one or just consumed one.&lt;/p&gt;

&lt;p&gt;The giveaway on API questions is whether you reach for the HTTP mechanism or hand-wave. "You'd use a POST" is weaker than "POST, because creating a resource isn't idempotent and shouldn't be cached." For each answer below, notice the pattern: name the mechanism, give the one rule that matters, then stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP methods &amp;amp; status codes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between GET, POST, PUT, PATCH and DELETE?
&lt;/h3&gt;

&lt;p&gt;They map to intent. &lt;code&gt;GET&lt;/code&gt; reads (and must have no side effects). &lt;code&gt;POST&lt;/code&gt; creates a new resource or triggers an action. &lt;code&gt;PUT&lt;/code&gt; replaces a resource entirely. &lt;code&gt;PATCH&lt;/code&gt; updates part of one. &lt;code&gt;DELETE&lt;/code&gt; removes it. The property that matters is &lt;strong&gt;idempotency&lt;/strong&gt;: GET, PUT and DELETE are idempotent (calling them twice leaves the same state), POST is not (two POSTs create two resources). That's why a retried POST can double-create and a retried PUT can't.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you pick a verb by its semantics and idempotency, not habit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which status codes do you use, and when?
&lt;/h3&gt;

&lt;p&gt;The families first: &lt;code&gt;2xx&lt;/code&gt; success, &lt;code&gt;4xx&lt;/code&gt; the client's fault, &lt;code&gt;5xx&lt;/code&gt; the server's fault. The ones you should name without thinking: &lt;code&gt;200&lt;/code&gt; OK, &lt;code&gt;201&lt;/code&gt; Created (with the new resource), &lt;code&gt;204&lt;/code&gt; No Content (a successful DELETE), &lt;code&gt;400&lt;/code&gt; Bad Request (malformed), &lt;code&gt;401&lt;/code&gt; Unauthorized (not authenticated), &lt;code&gt;403&lt;/code&gt; Forbidden (authenticated but not allowed), &lt;code&gt;404&lt;/code&gt; Not Found, &lt;code&gt;409&lt;/code&gt; Conflict, &lt;code&gt;422&lt;/code&gt; Unprocessable (validation failed), &lt;code&gt;429&lt;/code&gt; Too Many Requests, and &lt;code&gt;500&lt;/code&gt; for an unhandled server error. The classic mistake is returning &lt;code&gt;200&lt;/code&gt; with an error in the body — the status code &lt;em&gt;is&lt;/em&gt; the result.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you use the status line to carry meaning, especially the 401-vs-403 distinction.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What actually makes an API "RESTful"?
&lt;/h3&gt;

&lt;p&gt;REST is a set of constraints, and the ones interviewers care about are: resources identified by URLs, standard HTTP verbs for actions on them, and — the big one — &lt;strong&gt;statelessness&lt;/strong&gt;: every request carries everything the server needs, so the server keeps no per-client session between calls. Statelessness is what lets you put any server behind a load balancer and scale horizontally. A representation (usually JSON) is what you actually send over the wire.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you tie "RESTful" to statelessness and scaling, not just "it returns JSON."&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you design resource URLs?
&lt;/h3&gt;

&lt;p&gt;Nouns, not verbs, and plural collections: &lt;code&gt;/users&lt;/code&gt;, &lt;code&gt;/users/42&lt;/code&gt;, &lt;code&gt;/users/42/orders&lt;/code&gt;. The HTTP method is the verb, so &lt;code&gt;GET /users/42&lt;/code&gt; — never &lt;code&gt;GET /getUser?id=42&lt;/code&gt;. Nest to show a relationship (&lt;code&gt;/users/42/orders&lt;/code&gt;) but don't nest more than a level or two, or URLs get unwieldy. Use query parameters for filtering, sorting and pagination (&lt;code&gt;/orders?status=open&amp;amp;sort=-created&lt;/code&gt;), not for identity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that the URL names the resource and the method names the action — the core REST idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  PUT vs PATCH — what's the difference?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PUT&lt;/code&gt; replaces the whole resource — you send the complete representation, and anything you leave out is cleared. &lt;code&gt;PATCH&lt;/code&gt; applies a partial update — you send only the fields that change. Practically, PATCH is what you usually want for an "edit"; PUT is for a full overwrite. The subtlety interviewers like: PUT is idempotent (sending the same full body twice is fine), while a carelessly-designed PATCH (say, "increment by 1") may not be.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know partial vs full update, and the idempotency corner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning, pagination &amp;amp; errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you version an API?
&lt;/h3&gt;

&lt;p&gt;You version so you can change the contract without breaking existing clients. The common approaches: a URL prefix (&lt;code&gt;/v1/users&lt;/code&gt;) — simplest and most visible; a header or media type (&lt;code&gt;Accept: application/vnd.api+json;version=1&lt;/code&gt;) — cleaner URLs but less obvious. URL versioning wins most interviews for being explicit. The real point is that &lt;em&gt;any&lt;/em&gt; breaking change — removing a field, renaming one, changing a type — needs a new version; additive changes don't.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you protect existing clients and know what counts as a breaking change.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you paginate a large collection?
&lt;/h3&gt;

&lt;p&gt;Two options. &lt;strong&gt;Offset pagination&lt;/strong&gt; (&lt;code&gt;?limit=20&amp;amp;offset=40&lt;/code&gt;) is simple but gets slow on deep pages and can skip or repeat rows if the data changes between requests. &lt;strong&gt;Cursor (keyset) pagination&lt;/strong&gt; (&lt;code&gt;?limit=20&amp;amp;after=&amp;lt;id&amp;gt;&lt;/code&gt;) passes a pointer to the last item you saw and queries "the next 20 after this" — it stays fast at any depth and is stable under inserts. Use offset for small admin tables, cursor for large or live feeds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know offset breaks down at scale and cursor is the fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  How should an API return errors?
&lt;/h3&gt;

&lt;p&gt;With the right status code &lt;em&gt;and&lt;/em&gt; a consistent, machine-readable body — a stable error code, a human-readable message, and ideally which field failed: &lt;code&gt;{ "error": "validation_failed", "message": "...", "field": "email" }&lt;/code&gt;. Consistency is the whole point: a client should be able to handle every error the same way. Don't leak internals (stack traces, SQL) in the message, and don't return &lt;code&gt;200&lt;/code&gt; for a failure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that your errors are predictable and safe for a client to program against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth, caching &amp;amp; reliability
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you authenticate a REST API?
&lt;/h3&gt;

&lt;p&gt;Pick by use case. &lt;strong&gt;API keys&lt;/strong&gt; for server-to-server — simple, long-lived, carried in a header. &lt;strong&gt;JWT (bearer tokens)&lt;/strong&gt; for user sessions in a stateless API — the token is signed and self-contained, so the server verifies it without a lookup, which fits REST's statelessness. &lt;strong&gt;OAuth 2.0&lt;/strong&gt; when a third party acts on a user's behalf ("sign in with Google," granting another app access). Whatever you choose, it's HTTPS-only and the credential goes in the &lt;code&gt;Authorization&lt;/code&gt; header, never the URL.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you match the auth method to the scenario and never put secrets in a URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  REST vs GraphQL — when would you use each?
&lt;/h3&gt;

&lt;p&gt;REST exposes resources at fixed endpoints; GraphQL exposes one endpoint and lets the client ask for exactly the fields it wants. GraphQL shines when clients need very different shapes of data and you want to avoid over-fetching or many round-trips — typically rich frontends. REST wins on simplicity, caching (HTTP caching is built in), and tooling, and is usually the right default for a straightforward resource API. The honest answer: REST unless you have the specific over-fetching / many-clients problem GraphQL solves.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you don't pick GraphQL for hype — you justify it with a concrete fetching problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is idempotency, and how do you make an unsafe endpoint safe to retry?
&lt;/h3&gt;

&lt;p&gt;An operation is idempotent if doing it twice has the same effect as once. It matters because clients retry on timeouts, and a timeout doesn't tell you whether the first call succeeded — so a non-idempotent "create order" or "charge card" can run twice. The fix is an &lt;strong&gt;idempotency key&lt;/strong&gt;: the client sends a unique ID in a header, the server records it, and a repeat with the same key returns the original result instead of acting again. Any state-changing POST that can be retried needs this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you design for retries and at-least-once delivery, not a perfect network.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does HTTP caching work for an API?
&lt;/h3&gt;

&lt;p&gt;Two mechanisms. &lt;code&gt;Cache-Control&lt;/code&gt; headers tell clients and proxies how long a response may be reused (&lt;code&gt;max-age&lt;/code&gt;), which is great for data that rarely changes. &lt;code&gt;ETag&lt;/code&gt; plus &lt;code&gt;If-None-Match&lt;/code&gt; does conditional requests: the server sends a version tag, the client sends it back, and the server replies &lt;code&gt;304 Not Modified&lt;/code&gt; with no body if nothing changed — saving bandwidth. Caching is one of REST's real advantages over RPC-style APIs, and it only works cleanly because GET is safe and idempotent.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you can cut load with HTTP's own caching, and know it depends on safe methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;The strongest API answers are short and mechanical: name the verb or status code, give the one rule (idempotency, statelessness, the breaking-change line), and stop. Resist listing every status code you know — the interviewer is checking judgement, not recall. If you're unsure of an exact code, reason from the family: "it's a client error, so 4xx, and since it's a validation failure specifically, 422."&lt;/p&gt;

&lt;p&gt;Two habits that quietly raise your score: &lt;strong&gt;tie every choice to a property&lt;/strong&gt; (why POST and not PUT? because create isn't idempotent), and &lt;strong&gt;mention statelessness unprompted&lt;/strong&gt; when scaling comes up — it signals you've actually run an API in production. Prepping the whole backend stack? Pair this with the &lt;a href="https://peakblick.com/blog/sql-interview-questions.html" rel="noopener noreferrer"&gt;SQL&lt;/a&gt;, &lt;a href="https://peakblick.com/blog/python-backend-interview-questions.html" rel="noopener noreferrer"&gt;Python backend&lt;/a&gt;, and &lt;a href="https://peakblick.com/blog/system-design-interview-questions.html" rel="noopener noreferrer"&gt;system design&lt;/a&gt; questions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Rehearse these out loud, scored.&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article8" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; asks you REST API questions like these on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real interview. Free to try, no card.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>interview</category>
      <category>career</category>
    </item>
    <item>
      <title>System Design Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Tue, 06 Oct 2026 18:34:08 +0000</pubDate>
      <link>https://dev.to/peakblick/system-design-interview-questions-with-model-answers-533m</link>
      <guid>https://dev.to/peakblick/system-design-interview-questions-with-model-answers-533m</guid>
      <description>&lt;p&gt;These are the system design questions that actually come up — grouped by topic, each with a short model answer and a note on what the interviewer is really checking. System design rewards judgement over facts: there's rarely one right answer, only trade-offs you can name and defend.&lt;/p&gt;

&lt;p&gt;The trap in a system design round is treating it like trivia. The interviewer isn't checking whether you've memorised what a load balancer is — they're checking whether you can reason about scale, spot the bottleneck, and make a call while naming what it costs. For each answer below, notice the pattern: name the mechanism, give the one trade-off that matters, then say when you'd actually reach for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling fundamentals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between vertical and horizontal scaling?
&lt;/h3&gt;

&lt;p&gt;Vertical scaling (scaling up) means a bigger machine — more CPU, RAM, disk. It's simple and needs no code changes, but it has a hard ceiling and a single point of failure. Horizontal scaling (scaling out) means more machines behind a load balancer. It scales much further and adds redundancy, but it forces you to deal with distributed-system problems: shared state, consistency, and coordination. Short version: scale up until it's too expensive or too risky, then scale out.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know scaling out buys you headroom at the cost of complexity, not for free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency vs throughput — what's the difference?
&lt;/h3&gt;

&lt;p&gt;Latency is how long one request takes; throughput is how many requests you handle per second. They're related but not the same — you can raise throughput by adding workers while a single request stays just as slow, and you can cut latency without touching throughput. Know which one the problem is about: a slow page is a latency problem, a system falling over under load is a throughput problem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you reach for the right lever instead of "make it faster" in general.&lt;/p&gt;

&lt;h3&gt;
  
  
  A service is getting too much traffic. How do you think about handling it?
&lt;/h3&gt;

&lt;p&gt;Measure first — find the actual bottleneck instead of guessing. Then work the usual order: cache what's read often and changes rarely; scale the stateless parts horizontally behind a load balancer; move slow or spiky work off the request path into a queue; and only then scale the database, which is usually the hardest piece to scale. The instinct interviewers want is "find the bottleneck, then apply the cheapest fix that moves it," not "add servers everywhere."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a systematic approach, and knowing the database is usually the real constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching &amp;amp; data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is caching, and where would you add it?
&lt;/h3&gt;

&lt;p&gt;A cache stores the result of expensive work close to where it's needed, so you skip the work next time. You add it wherever reads dominate and the data tolerates being slightly stale — a database query layer, rendered pages, a CDN for static assets, or in-memory (Redis/Memcached) for hot keys. The win is latency and load; the cost is a second copy of the truth that can drift.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you see caching as a read optimisation with a staleness price, not a free speed-up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is cache invalidation hard?
&lt;/h3&gt;

&lt;p&gt;Because a cache is a copy, and the moment the source changes, the copy is wrong — you have to decide when to throw it away without knowing in advance when the data will change. The common strategies are TTL (expire after N seconds — simple, but serves stale data in that window) and explicit invalidation on write (fresh, but easy to miss a path and leak stale data forever). There's no clean answer, which is why it's the classic "two hard things in computer science" joke.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand the real cost of a cache is correctness, not just the extra moving part.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL vs NoSQL — when would you choose each?
&lt;/h3&gt;

&lt;p&gt;Reach for SQL (a relational database) by default: you get strong consistency, transactions, flexible queries and joins, and decades of tooling — ideal when your data is related and correctness matters, like anything with money or accounts. Reach for NoSQL when you have a specific scale or shape problem SQL handles badly: massive write throughput, a flexible/changing schema, or simple key-value access patterns at scale. The honest answer in most interviews is "SQL unless I can name the specific reason it won't work."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you don't pick NoSQL for hype — you justify it with a concrete constraint.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is sharding, and what's the hard part?
&lt;/h3&gt;

&lt;p&gt;Sharding splits one dataset across multiple databases by some key (user ID, region) so no single machine holds — or serves — all of it. That's how you scale writes past one box. The hard part is choosing the shard key: a bad one creates hot shards (one machine does most of the work) and makes cross-shard queries and joins painful or impossible. Resharding later is a major operation, so the key is a decision you want to get right early.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know sharding scales writes but the shard key is where it lives or dies.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does replication give you, and what's the trade-off?
&lt;/h3&gt;

&lt;p&gt;Replication keeps copies of your data on multiple machines. It buys you two things: read scaling (spread reads across replicas) and availability (if one node dies, another has the data). The trade-off is consistency: with asynchronous replication, a replica can lag behind the primary, so a read right after a write might return stale data. You're trading "always perfectly up to date" for "more reads and survives failures."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you connect replication to the consistency lag it introduces, not just "backups."&lt;/p&gt;

&lt;h3&gt;
  
  
  What does the CAP theorem actually force you to choose?
&lt;/h3&gt;

&lt;p&gt;CAP says that when a network partition happens — some nodes can't talk to others — a distributed system has to pick: stay &lt;strong&gt;consistent&lt;/strong&gt; (reject or block requests it can't confirm, so no one sees wrong data) or stay &lt;strong&gt;available&lt;/strong&gt; (keep answering, accepting that different nodes might briefly disagree). You don't get both during a partition. The useful framing isn't "CA vs CP vs AP" trivia — it's "when the network splits, would I rather return an error or risk a stale answer?", and that depends on whether you're moving money or showing a feed.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you can turn CAP into a real product decision, not recite three letters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication &amp;amp; scale
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does a load balancer do?
&lt;/h3&gt;

&lt;p&gt;It sits in front of a pool of identical servers and spreads incoming requests across them, so no single server is overwhelmed and the pool can grow or shrink freely. It also does health checks — stop sending traffic to a server that's down — which is half the reason it exists. For it to work, your servers generally need to be stateless (any server can handle any request), which is why "where does the session live?" is the follow-up question.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you connect load balancing to statelessness, not just "it shares the load."&lt;/p&gt;

&lt;h3&gt;
  
  
  When would you use a message queue?
&lt;/h3&gt;

&lt;p&gt;When work doesn't need to happen inside the request. A queue lets you accept a request, drop a job on the queue, respond immediately, and process it later with separate workers — think sending email, generating a report, processing an upload. It buys you three things: a faster response, resilience to spikes (the queue absorbs the burst), and decoupling (the producer doesn't care who consumes). The cost is that the work is now asynchronous, so you design for "eventually done" and for retries.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you move slow or spiky work off the request path on purpose, and handle the async consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  How would you rate-limit an API?
&lt;/h3&gt;

&lt;p&gt;Track requests per client (by API key or IP) over a time window and reject once they exceed the limit, usually with a &lt;code&gt;429 Too Many Requests&lt;/code&gt;. The common algorithm is a token bucket — each client has a bucket that refills at a steady rate and each request spends a token — which allows short bursts while capping the sustained rate. In a multi-server setup the counter has to live somewhere shared, like Redis, so the limit holds across the whole fleet rather than per server.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you remember the limit has to be enforced globally, not per instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a CDN for?
&lt;/h3&gt;

&lt;p&gt;A CDN caches your static content — images, CSS, JS, video — on servers spread around the world, so a user in Tokyo is served from a nearby edge instead of your origin in Virginia. That cuts latency dramatically and takes load off your servers. The trade-off is the usual cache one: you have to handle invalidation (cache-busting filenames or purges) when the content changes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you push static, geographically-shared content to the edge and know how you'd update it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability &amp;amp; approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is idempotency, and why does it matter in a distributed system?
&lt;/h3&gt;

&lt;p&gt;An operation is idempotent if doing it twice has the same effect as doing it once. It matters because in a distributed system, requests get retried — a network timeout doesn't tell you whether the first attempt succeeded. If "charge this card" isn't idempotent, a retry double-charges. The fix is an idempotency key: the client sends a unique ID with the request, and the server records it so a repeat with the same key is recognised and ignored. Any operation that changes state and can be retried needs this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you design for retries and at-least-once delivery, not a perfect network.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you approach an open-ended "design X" question?
&lt;/h3&gt;

&lt;p&gt;Don't start drawing boxes. Start by scoping: ask about the requirements and scale — how many users, read-heavy or write-heavy, what has to be consistent, what can be eventual. Sketch the core data model and the main API, then the high-level components (clients, load balancer, services, database, cache, queue). &lt;em&gt;Then&lt;/em&gt; find the bottleneck for the given scale and go deep on one or two pieces — how you'd cache, shard, or queue — naming the trade-off at each step. The interviewer is grading the conversation, not a finished diagram.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you drive the problem — scope, structure, then depth — instead of memorising one architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;System design is the round where candidates most often sink themselves by saying too much. The strong move is the opposite: scope before you build, name the trade-off instead of claiming a choice is just "better," and admit what you don't know rather than bluff — "I'd need to check the real numbers, but my instinct is to shard by user ID because our access pattern is per-user" reads far better than a confident wrong architecture.&lt;/p&gt;

&lt;p&gt;Three habits that quietly raise your score: &lt;strong&gt;always ask about scale first&lt;/strong&gt; (the right design for 1,000 users is wrong for 100 million); &lt;strong&gt;say the trade-off out loud&lt;/strong&gt; for every choice; and &lt;strong&gt;drive the conversation&lt;/strong&gt; — a good system design answer is a discussion you lead, not a monologue. Prepping the backend stack too? Pair this with the &lt;a href="https://peakblick.com/blog/sql-interview-questions.html" rel="noopener noreferrer"&gt;SQL&lt;/a&gt;, &lt;a href="https://peakblick.com/blog/python-backend-interview-questions.html" rel="noopener noreferrer"&gt;Python backend&lt;/a&gt;, and &lt;a href="https://peakblick.com/blog/django-interview-questions.html" rel="noopener noreferrer"&gt;Django&lt;/a&gt; questions.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Rehearse these out loud, scored.&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article7" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; asks you system design questions like these on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real interview. Free to try, no card.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>interview</category>
      <category>career</category>
      <category>backend</category>
    </item>
    <item>
      <title>How We Built Our Own AI Interview Grader (No GPT)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Mon, 05 Oct 2026 18:48:43 +0000</pubDate>
      <link>https://dev.to/peakblick/how-we-built-our-own-ai-interview-grader-no-gpt-58ja</link>
      <guid>https://dev.to/peakblick/how-we-built-our-own-ai-interview-grader-no-gpt-58ja</guid>
      <description>&lt;p&gt;Most "AI interview" tools are a thin wrapper around GPT. We did the opposite — we trained our own generative model, from scratch, to run the whole interview: ask the questions, grade your answers, and explain them. Here's how it works, what went wrong on the way, and why the harder road was worth it.&lt;/p&gt;

&lt;p&gt;When you finish an answer in Peakblick, something has to decide: did that actually cover what the question was testing, or did it just sound confident? Doing that well — on a timer, for every answer, consistently — is the whole product. Everything else is a frontend. So we spent our time on the thing that's actually hard: the model that runs the conversation and judges what you say.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just call GPT?
&lt;/h2&gt;

&lt;p&gt;It's the obvious shortcut, and plenty of "AI interview" products take it: forward your answer to a big API, ask it to score, print the result. We didn't, for three reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency.&lt;/strong&gt; A grader built on someone else's API quietly changes every time that model is updated. The same answer can score a 6 one week and an 8 the next, and you have no way to know why. For a tool whose entire job is to give you a trustworthy read on where you stand, that drift is disqualifying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost.&lt;/strong&gt; Grading every answer of every mock interview through a frontier API doesn't scale to something people use daily without the price landing back on the user. We wanted practice to be cheap enough to do constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Independence.&lt;/strong&gt; We wanted a system we own, can measure end to end, and can improve on our own schedule — not rent from a provider who can change the terms, the price, or the behaviour overnight. A side effect we like: your answers are handled entirely by our own model and never shipped off to a third party.&lt;/p&gt;

&lt;p&gt;So we trained one ourselves — a compact, from-scratch generative model, with no distillation from GPT or any other external model. Every improvement is ours, and so was every problem. That constraint made what came next much harder, and much more interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  One generative model, the whole interview
&lt;/h2&gt;

&lt;p&gt;Peakblick runs on a single model we built and trained. It is &lt;strong&gt;fully generative&lt;/strong&gt; — the same model that generates the questions also generates a model answer and writes your feedback. There's no external API anywhere in the loop, and nothing hard-coded from a question bank: it produces the interview the way a good interviewer improvises one, shaped by the role, seniority and topics you picked.&lt;/p&gt;

&lt;p&gt;That matters because a real interview isn't a quiz with fixed answers. A strong senior backend question looks nothing like a junior one on the same topic, and a good interviewer reads your answer and decides what's worth probing next. We wanted a model that behaves like that — not one that reads lines off a card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the questions
&lt;/h2&gt;

&lt;p&gt;The first job is asking well. Given a role and level, the model plans the ground to cover and then generates each question fresh — phrased naturally, pitched at the right difficulty, and varied so you're not drilling the same three prompts every session. Generating questions is the part a generative model is genuinely great at, and it's where the "no fixed bank" promise pays off: the space of questions is effectively unlimited, so practice stays useful past the first few runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part: judging without hallucinating
&lt;/h2&gt;

&lt;p&gt;Asking is the easy half. The hard half is judgement — and it's where most of our work went.&lt;/p&gt;

&lt;p&gt;A compact model asked to grade cold can do exactly what a nervous candidate does: sound fluent and confident while being wrong. Early versions of our grader had a specific, dangerous failure mode — faced with an answer that was actually incorrect, the model would sometimes write a confident, well-structured explanation of why it was &lt;em&gt;right&lt;/em&gt;. It talked itself into approving bad answers. And that's the one failure a grader can't have: tell someone a wrong answer was good, and you've done worse than not grading at all. You've taught them the wrong thing right before the real interview.&lt;/p&gt;

&lt;p&gt;Getting that reliable — not just good on average, but trustworthy on the answers that matter — was the real engineering problem behind Peakblick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our approach: let the model answer first
&lt;/h2&gt;

&lt;p&gt;The fix that made judgement reliable is simple, and it plays directly to what a generative model is best at. Before it ever looks at &lt;em&gt;your&lt;/em&gt; answer, the model &lt;strong&gt;writes its own ideal answer&lt;/strong&gt; to that exact question — what a strong response should actually cover, generated fresh for this question rather than pulled from a list.&lt;/p&gt;

&lt;p&gt;Only then does it read your answer, and measure it against that reference: point by point, what you covered, what you touched lightly, what you missed, and where you said something that contradicts a key idea. Grounding the grade in the model's own generated answer — instead of asking it to pass judgement out of thin air — is what turned a shaky grader into one we trust. The model is doing what it's good at (writing a strong answer, then comparing) instead of conjuring a verdict from nothing. It's the same reason a human grader with the model solution in front of them is fairer than one marking from memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  A score you can read, not a mystery number
&lt;/h2&gt;

&lt;p&gt;Because the grade is built up point by point, the final 1–10 isn't a vibe — it's the sum of concrete judgements you can see. A 6 means something specific: these points landed, this one was thin, that one was missing. That's what lets the feedback be useful instead of generic. "Good effort, keep practising" helps no one; "you explained the mechanism but missed the one trade-off the interviewer was really after" is something you can actually fix before Monday.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback that's specific, not generic
&lt;/h2&gt;

&lt;p&gt;Feedback is only worth anything if it names the real gap. Ours is &lt;strong&gt;grounded&lt;/strong&gt; — tied to what the question was actually testing and to the model answer it laid out — so it tells you exactly where you fell short and why it mattered, instead of hand-waving. It never pads with invented criticism or "good effort, keep practising" filler.&lt;/p&gt;

&lt;p&gt;Every line points at something concrete you can fix: the trade-off you skipped, the edge case you didn't mention, the term you reached for that wasn't quite right. That specificity is the whole difference between feedback you nod along to and feedback you actually act on — and acting on it, before the real interview, is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching it the whole stack
&lt;/h2&gt;

&lt;p&gt;A grader is only as good as what it knows. A model can't fairly judge an answer about database indexes, or Git internals, or how a framework handles requests, if it doesn't deeply understand those things itself. So a large and ongoing part of the work isn't the model's wiring at all — it's its education.&lt;/p&gt;

&lt;p&gt;We train it on a broad, carefully-sourced body of real technical material across the stack: multiple languages, the frameworks people actually ship with, databases and SQL, system design, version control, and the fundamentals underneath all of it. As we widen that knowledge, the grading gets fairer and holds up across more of the topics a real interview might wander into. The architecture stays the same; the knowledge keeps growing — and that's a dial we can keep turning for as long as the product lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means — and what's next
&lt;/h2&gt;

&lt;p&gt;The headline isn't "we use AI." Everyone does. It's that the thing running your interview is a generative model &lt;em&gt;we&lt;/em&gt; built, own, measure, and keep improving — not a wrapper whose behaviour we can't see or steer. That ownership is why we can promise consistency, keep practice cheap, and keep raising the ceiling instead of waiting for someone else's roadmap.&lt;/p&gt;

&lt;p&gt;Next, we're broadening both ends: more topics and seniorities on the question side, and deeper knowledge on the grading side — including interviews that dig into real code. The goal is simple and a little ambitious: an interviewer in your pocket that's good enough to make the real one feel easy.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Try it on your own answers.&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article6" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; asks you real interview questions on a timer and scores every answer 1–10 with specific feedback — so you find the weak spots before the real thing. Free to try, no card.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>interview</category>
      <category>machinelearning</category>
      <category>startup</category>
    </item>
    <item>
      <title>Git Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Sat, 03 Oct 2026 20:02:15 +0000</pubDate>
      <link>https://dev.to/peakblick/git-interview-questions-with-model-answers-88c</link>
      <guid>https://dev.to/peakblick/git-interview-questions-with-model-answers-88c</guid>
      <description>&lt;p&gt;Git questions look like trivia, but they're really testing one thing: &lt;strong&gt;do you have a mental model of what Git is doing, or do you memorise commands and hope?&lt;/strong&gt; An interviewer can tell the difference in one sentence. "Rebase rewrites history" is a memorised fact; "rebase replays your commits as &lt;em&gt;new&lt;/em&gt; commits with new hashes, which is why you never do it to shared branches" is a model.&lt;/p&gt;

&lt;p&gt;Below are the questions that come up in almost every developer interview, grouped by topic. Each has a short model answer, a command example where it helps, and a note on what the interviewer is really checking. Notice the pattern in every answer: &lt;strong&gt;name the mechanism, give the one rule or trade-off that matters, then stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A quick foundation that makes the rest click: a &lt;strong&gt;branch is just a movable pointer to a commit&lt;/strong&gt;, a commit is a snapshot plus a parent link, and &lt;code&gt;HEAD&lt;/code&gt; points to "where you are now". Almost every answer below is a consequence of those three facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between &lt;code&gt;git fetch&lt;/code&gt; and &lt;code&gt;git pull&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;fetch&lt;/code&gt; downloads the latest commits and refs from the remote but &lt;strong&gt;doesn't touch your working branch&lt;/strong&gt; — it updates your remote-tracking refs (&lt;code&gt;origin/main&lt;/code&gt;) so you can inspect what changed first. &lt;code&gt;pull&lt;/code&gt; is &lt;code&gt;fetch&lt;/code&gt; &lt;strong&gt;plus&lt;/strong&gt; an automatic merge (or rebase) of those changes into your current branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin            &lt;span class="c"&gt;# safe: just updates origin/*, nothing merged&lt;/span&gt;
git log HEAD..origin/main   &lt;span class="c"&gt;# see what you'd be pulling in&lt;/span&gt;
git pull                    &lt;span class="c"&gt;# fetch + merge (or --rebase) into your branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;fetch&lt;/code&gt; is the look-before-you-leap option; &lt;code&gt;pull&lt;/code&gt; is the convenience that can surprise you with an unexpected merge commit. Many teams set &lt;code&gt;git config pull.rebase true&lt;/code&gt; to keep history linear.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand local vs. remote state, not just the two commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the staging area (the index)?
&lt;/h3&gt;

&lt;p&gt;It's an intermediate layer between your working directory and the repository. &lt;code&gt;git add&lt;/code&gt; moves changes into staging; &lt;code&gt;git commit&lt;/code&gt; records &lt;strong&gt;exactly what's staged&lt;/strong&gt; — not what's in your working directory. That extra step is what lets you craft a commit: stage only some files, or even part of a file with &lt;code&gt;git add -p&lt;/code&gt;, instead of committing everything at once.&lt;/p&gt;

&lt;p&gt;Git's three "trees" are the &lt;strong&gt;working directory&lt;/strong&gt;, the &lt;strong&gt;index&lt;/strong&gt; (staging), and &lt;strong&gt;&lt;code&gt;HEAD&lt;/code&gt;&lt;/strong&gt; (the last commit). Nearly every confusing Git command makes sense once you ask "which of the three trees does this touch?"&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; the three-tree model — the foundation for &lt;code&gt;reset&lt;/code&gt;, &lt;code&gt;diff&lt;/code&gt;, and clean commits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge vs. rebase — what's the difference, and the rule?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;merge&lt;/code&gt; combines two branches with a &lt;strong&gt;merge commit&lt;/strong&gt;, keeping history exactly as it happened (non-linear, but truthful). &lt;code&gt;rebase&lt;/code&gt; &lt;strong&gt;replays your commits on top of another branch&lt;/strong&gt;, producing a clean linear history — but it creates &lt;em&gt;new&lt;/em&gt; commits with new hashes; the originals are abandoned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout feature
git rebase main       &lt;span class="c"&gt;# replay feature's commits on top of main&lt;/span&gt;
&lt;span class="c"&gt;# vs&lt;/span&gt;
git merge main        &lt;span class="c"&gt;# create a merge commit joining the two&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The golden rule: &lt;strong&gt;rebase your own local, unpushed work to tidy it up; never rebase a branch others have already pulled.&lt;/strong&gt; Rewriting shared history forces everyone else to reconcile commits that no longer exist in your branch.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you understand history rewriting and the one rule that keeps a team sane.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is &lt;code&gt;HEAD&lt;/code&gt;, and what's a "detached HEAD"?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;HEAD&lt;/code&gt; is a pointer to your current location — normally it points at a branch, which points at a commit. A &lt;strong&gt;detached HEAD&lt;/strong&gt; is when &lt;code&gt;HEAD&lt;/code&gt; points &lt;em&gt;directly&lt;/em&gt; at a commit instead of a branch, which happens when you &lt;code&gt;git checkout &amp;lt;commit-hash&amp;gt;&lt;/code&gt; or a tag. You can look around and even commit, but those commits aren't on any branch — if you walk away, they're hard to find again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout a1b2c3d     &lt;span class="c"&gt;# detached: HEAD -&amp;gt; commit, not a branch&lt;/span&gt;
git switch &lt;span class="nt"&gt;-c&lt;/span&gt; fix-here   &lt;span class="c"&gt;# rescue: make a branch so commits aren't lost&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you really grasp "a branch is a pointer" — detached HEAD is the edge case that proves it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branching &amp;amp; merging
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a fast-forward merge?
&lt;/h3&gt;

&lt;p&gt;When the branch you're merging into &lt;strong&gt;hasn't diverged&lt;/strong&gt; — your target is strictly behind the branch you're merging — Git doesn't need a merge commit; it just moves the branch pointer forward. That's a fast-forward. If the branches &lt;em&gt;have&lt;/em&gt; diverged, Git has to create a real merge commit. Use &lt;code&gt;git merge --no-ff&lt;/code&gt; when you &lt;em&gt;want&lt;/em&gt; a merge commit even on a fast-forward, to keep feature branches visible in history.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; the pointer model of branches, again.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you resolve a merge conflict?
&lt;/h3&gt;

&lt;p&gt;When two branches change the same lines, Git marks the conflict in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
your version
=======
their version
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You open the file, edit it into the result you actually want, &lt;strong&gt;delete the markers&lt;/strong&gt;, then stage and continue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add resolved_file.py
git commit                 &lt;span class="c"&gt;# or: git rebase --continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tooling doesn't decide for you — resolving a conflict is a human judgement about what the code should be. &lt;code&gt;git merge --abort&lt;/code&gt; backs out entirely if you want to start over.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you've actually done this, not just read about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;git cherry-pick&lt;/code&gt; do, and when would you use it?
&lt;/h3&gt;

&lt;p&gt;It applies the changes from a &lt;strong&gt;specific commit&lt;/strong&gt; onto your current branch, as a new commit. Useful for pulling one bug-fix commit from another branch without merging the whole thing — a hotfix that needs to go to both &lt;code&gt;main&lt;/code&gt; and a release branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick a1b2c3d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade-off: it duplicates the change as a new commit, so overusing it creates divergent copies that are annoying to reconcile later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you can move a single change around deliberately, not just merge whole branches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Undoing things
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;git revert&lt;/code&gt; vs. &lt;code&gt;git reset&lt;/code&gt; — when do you use each?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;revert&lt;/code&gt; creates a &lt;strong&gt;new&lt;/strong&gt; commit that undoes a previous one, leaving history intact — safe for anything already pushed. &lt;code&gt;reset&lt;/code&gt; &lt;strong&gt;moves the branch pointer backwards&lt;/strong&gt;, rewriting history — for local, unpushed work only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git revert a1b2c3d       &lt;span class="c"&gt;# public, safe: adds an "undo" commit&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; HEAD~1  &lt;span class="c"&gt;# private: discards the last commit entirely&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule mirrors rebase: &lt;strong&gt;revert what's public, reset what's private.&lt;/strong&gt; Using &lt;code&gt;reset&lt;/code&gt; on a pushed branch and force-pushing is how you ruin a teammate's afternoon.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; the difference between a safe undo and a destructive one.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do &lt;code&gt;--soft&lt;/code&gt;, &lt;code&gt;--mixed&lt;/code&gt; and &lt;code&gt;--hard&lt;/code&gt; do on &lt;code&gt;git reset&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;All three move &lt;code&gt;HEAD&lt;/code&gt; to the commit you name; they differ in how much they touch afterwards — which maps straight onto the three trees:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Moves HEAD&lt;/th&gt;
&lt;th&gt;Index (staging)&lt;/th&gt;
&lt;th&gt;Working dir&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--soft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;kept (staged)&lt;/td&gt;
&lt;td&gt;kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--mixed&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;reset (unstaged)&lt;/td&gt;
&lt;td&gt;kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--hard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;reset&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;discarded&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So &lt;code&gt;--soft&lt;/code&gt; is for re-committing (e.g. squashing the last few commits into one), &lt;code&gt;--mixed&lt;/code&gt; un-stages, and &lt;code&gt;--hard&lt;/code&gt; is the one that &lt;strong&gt;loses work&lt;/strong&gt; — reach for it deliberately.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; precision, and awareness that &lt;code&gt;--hard&lt;/code&gt; is destructive.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;git stash&lt;/code&gt; do?
&lt;/h3&gt;

&lt;p&gt;It shelves your uncommitted changes and reverts your working directory to a clean state, so you can switch context — jump to a branch for an urgent fix — without committing half-done work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git stash            &lt;span class="c"&gt;# shelve changes, clean working dir&lt;/span&gt;
git switch main      &lt;span class="c"&gt;# do the urgent thing&lt;/span&gt;
git switch -         &lt;span class="c"&gt;# back to your branch&lt;/span&gt;
git stash pop        &lt;span class="c"&gt;# reapply your shelved changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a workflow convenience, not part of history.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; everyday fluency, not theory.&lt;/p&gt;

&lt;h3&gt;
  
  
  You ran &lt;code&gt;git reset --hard&lt;/code&gt; and lost commits. Can you get them back?
&lt;/h3&gt;

&lt;p&gt;Usually yes — with &lt;code&gt;git reflog&lt;/code&gt;. The reflog records &lt;strong&gt;every move &lt;code&gt;HEAD&lt;/code&gt; makes&lt;/strong&gt;, even ones that aren't reachable from any branch anymore. Find the commit you were on and reset back to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog                 &lt;span class="c"&gt;# find the lost commit's hash&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; a1b2c3d   &lt;span class="c"&gt;# or: git branch rescue a1b2c3d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a great answer to give unprompted — it shows you know Git rarely &lt;em&gt;really&lt;/em&gt; deletes anything until garbage collection runs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; depth — most people don't know the reflog exists, and it's the safety net for every "destructive" command above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you find which commit introduced a bug?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt;. You mark a known-good commit and a known-bad one; Git checks out the midpoint, you test it and mark it, and it &lt;strong&gt;binary-searches&lt;/strong&gt; to the first bad commit in log-&lt;em&gt;n&lt;/em&gt; steps instead of checking every commit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad                 &lt;span class="c"&gt;# current commit is broken&lt;/span&gt;
git bisect good v1.2.0         &lt;span class="c"&gt;# this old tag worked&lt;/span&gt;
&lt;span class="c"&gt;# test each checkout, mark good/bad... or automate:&lt;/span&gt;
git bisect run pytest tests/test_thing.py
git bisect reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; knowing the right tool exists — and that it's a binary search, not a linear hunt.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you change the most recent commit?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;git commit --amend&lt;/code&gt; — to fix the message or add a file you forgot. But amending &lt;strong&gt;rewrites&lt;/strong&gt; that commit's hash, so only amend commits you haven't pushed. Amending a pushed commit forces everyone to reconcile a rewritten history — the same shared-history trap as rebase and reset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add forgotten_file.py
git commit &lt;span class="nt"&gt;--amend&lt;/span&gt; &lt;span class="nt"&gt;--no-edit&lt;/span&gt;   &lt;span class="c"&gt;# fold it into the last commit, keep the message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know "change the last commit" means rewriting it, with all that implies.&lt;/p&gt;

&lt;h3&gt;
  
  
  You committed a file that shouldn't be tracked (say, &lt;code&gt;.env&lt;/code&gt;). What now?
&lt;/h3&gt;

&lt;p&gt;Add it to &lt;code&gt;.gitignore&lt;/code&gt; and stop tracking it — note that &lt;code&gt;.gitignore&lt;/code&gt; alone &lt;strong&gt;won't&lt;/strong&gt; untrack a file that's already committed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; .env
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Stop tracking .env"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if it was a secret and it's already been pushed, it's now in the history forever: &lt;strong&gt;rotate the secret&lt;/strong&gt;. Removing it from the current tree doesn't remove it from past commits — you'd need a history rewrite with &lt;code&gt;git filter-repo&lt;/code&gt; or BFG, and even then anyone who already pulled still has it. The honest answer in an interview is "rotate first, clean history second".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; security awareness, and the gotcha that &lt;code&gt;.gitignore&lt;/code&gt; doesn't untrack.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;The difference between a pass and a fail on Git questions is &lt;strong&gt;showing a model, not reciting flags.&lt;/strong&gt; Say what the command does to the repo — pointers, history, the three trees — name the one rule that matters (rebase/reset only what's private), and stop.&lt;/p&gt;

&lt;p&gt;Three things that quietly raise your score:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reach for the mechanism, not the memorised command.&lt;/strong&gt; "I'd move the branch pointer back and keep the changes staged" beats blanking on whether that's &lt;code&gt;--soft&lt;/code&gt; or &lt;code&gt;--mixed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volunteer the safety net.&lt;/strong&gt; Mentioning the reflog, or "I'd rotate the secret first", signals real experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you forget an exact flag, describe the effect and say you'd check the docs.&lt;/strong&gt; That reads far better than a confident wrong flag.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Rehearse these out loud, scored.&lt;/strong&gt; &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article5" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; asks you Git questions like these on a timer and scores every answer 1–10 with feedback — so you find the weak spots before the real interview. Free to try, no card.&lt;/p&gt;

</description>
      <category>git</category>
      <category>interview</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Django Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Sun, 27 Sep 2026 21:55:58 +0000</pubDate>
      <link>https://dev.to/peakblick/django-interview-questions-with-model-answers-293f</link>
      <guid>https://dev.to/peakblick/django-interview-questions-with-model-answers-293f</guid>
      <description>&lt;p&gt;These are the Django questions that actually come up for backend and full-stack roles — grouped by topic, each with a short model answer and a note on what the interviewer is really checking.&lt;/p&gt;

&lt;p&gt;Django interviews reward a mental model over trivia. Interviewers want to hear that you understand &lt;em&gt;why&lt;/em&gt; the framework does something. For each answer below, notice the pattern: name the mechanism, give the one trade-off that matters, then stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Models &amp;amp; the ORM
&lt;/h2&gt;

&lt;h3&gt;
  
  
  QuerySets are lazy — when does one actually hit the database?
&lt;/h3&gt;

&lt;p&gt;A QuerySet doesn't run a query when you create or filter it — only when it's &lt;em&gt;evaluated&lt;/em&gt;: iterating it, calling &lt;code&gt;list()&lt;/code&gt;, &lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;bool()&lt;/code&gt;, or slicing with a step. That laziness lets you chain &lt;code&gt;.filter()&lt;/code&gt; calls into a single query. The flip side: if you re-use the same QuerySet in a loop or template it can re-run, so cache it with &lt;code&gt;list()&lt;/code&gt; when you'll touch it more than once.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you understand lazy evaluation — the root of most Django performance surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;select_related&lt;/code&gt; vs &lt;code&gt;prefetch_related&lt;/code&gt; (the N+1 problem)
&lt;/h3&gt;

&lt;p&gt;The N+1 problem is looping over a queryset and hitting the DB once per row for a related object — 1 + N queries. &lt;code&gt;select_related&lt;/code&gt; fixes it for FK / one-to-one with a SQL JOIN (one query). &lt;code&gt;prefetch_related&lt;/code&gt; handles many-to-many and reverse FKs with a second query that Python joins in memory. Reach for them whenever a loop or template touches related data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you can spot and fix the most common ORM bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between &lt;code&gt;null=True&lt;/code&gt; and &lt;code&gt;blank=True&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;null&lt;/code&gt; is database-level — the column may store &lt;code&gt;NULL&lt;/code&gt;. &lt;code&gt;blank&lt;/code&gt; is validation-level — forms and DRF serializers allow the field to be empty. They're independent. For text fields the convention is &lt;code&gt;blank=True&lt;/code&gt; &lt;em&gt;without&lt;/em&gt; &lt;code&gt;null=True&lt;/code&gt;, so "empty" is an empty string rather than two different empty states (&lt;code&gt;NULL&lt;/code&gt; and &lt;code&gt;''&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know the database layer and the validation layer are separate.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does &lt;code&gt;on_delete&lt;/code&gt; do on a &lt;code&gt;ForeignKey&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;It decides what happens to a row when the object it points to is deleted: &lt;code&gt;CASCADE&lt;/code&gt; deletes the children too, &lt;code&gt;PROTECT&lt;/code&gt; blocks the delete, &lt;code&gt;SET_NULL&lt;/code&gt; nulls the FK (needs &lt;code&gt;null=True&lt;/code&gt;), plus &lt;code&gt;SET_DEFAULT&lt;/code&gt; and &lt;code&gt;DO_NOTHING&lt;/code&gt;. It's a deliberate data-integrity choice — &lt;code&gt;CASCADE&lt;/code&gt; on the wrong relation can wipe data you meant to keep.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you think about referential integrity when you model data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Views, URLs &amp;amp; requests
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is middleware, and does the order matter?
&lt;/h3&gt;

&lt;p&gt;Middleware is a chain of hooks that wrap every request and response — sessions, auth, CSRF, security headers. Order matters: the request phase runs top-to-bottom through &lt;code&gt;MIDDLEWARE&lt;/code&gt;, the response phase bottom-to-top. So authentication must sit before anything that relies on &lt;code&gt;request.user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a mental model of the request pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function-based vs class-based views — which and when?
&lt;/h3&gt;

&lt;p&gt;FBVs are explicit and easy to read — good when the logic is one-off. CBVs use inheritance and mixins to reuse standard patterns (&lt;code&gt;ListView&lt;/code&gt;, &lt;code&gt;DetailView&lt;/code&gt;, generic CRUD), so you write less for common cases, but the flow is spread across the class hierarchy. Rule: FBV when the logic is unique, CBV when it's a standard pattern.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; judgment about reuse vs. readability, not dogma.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Django protect against CSRF?
&lt;/h3&gt;

&lt;p&gt;Django issues a per-session CSRF token. Unsafe methods (&lt;code&gt;POST&lt;/code&gt;/&lt;code&gt;PUT&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt;) must send it back — a hidden form field or the &lt;code&gt;X-CSRFToken&lt;/code&gt; header — and &lt;code&gt;CsrfViewMiddleware&lt;/code&gt; rejects a mismatch. An API authenticated by a token or JWT in the &lt;code&gt;Authorization&lt;/code&gt; header (not cookies) isn't vulnerable to cookie CSRF, which is why DRF token endpoints are often CSRF-exempt.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; security awareness and the difference between cookie and header auth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django specifics you'll be asked
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between a project and an app?
&lt;/h3&gt;

&lt;p&gt;A project is the whole site — settings, root URLs, config. An app is a self-contained, reusable module for one feature (say &lt;code&gt;accounts&lt;/code&gt; or &lt;code&gt;billing&lt;/code&gt;). One project contains many apps; a well-designed app does one thing and could drop into another project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you organise a codebase, not just make it work.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are migrations, and how do &lt;code&gt;makemigrations&lt;/code&gt; and &lt;code&gt;migrate&lt;/code&gt; differ?
&lt;/h3&gt;

&lt;p&gt;Migrations are version control for your DB schema. &lt;code&gt;makemigrations&lt;/code&gt; reads model changes and writes migration files — the plan. &lt;code&gt;migrate&lt;/code&gt; applies them to the database — the execution. You commit migration files to git so the whole team's schema stays in sync.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand the real schema-change workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django signals — when would you use them, and when avoid them?
&lt;/h3&gt;

&lt;p&gt;Signals let decoupled code react to events like &lt;code&gt;post_save&lt;/code&gt; — e.g. creating a profile when a user is created. Good for genuinely cross-cutting reactions. But they hide control flow: a save in one place silently triggers code elsewhere, which makes bugs hard to trace. If the logic clearly belongs in a model's &lt;code&gt;save()&lt;/code&gt; or a service function, call it directly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you weigh decoupling against traceability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production &amp;amp; performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A Django view is slow. How do you speed it up?
&lt;/h3&gt;

&lt;p&gt;Measure first — the Django Debug Toolbar or a query count shows where the time goes. Then, in order: kill N+1 with &lt;code&gt;select_related&lt;/code&gt;/&lt;code&gt;prefetch_related&lt;/code&gt;, fetch fewer columns with &lt;code&gt;only()&lt;/code&gt;/&lt;code&gt;values()&lt;/code&gt;, add a DB index on filtered/ordered columns, and only then cache if it's read-heavy. Don't reach for Redis before you've cut the queries.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a systematic process, not a memorised list of fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you handle concurrent updates safely?
&lt;/h3&gt;

&lt;p&gt;Wrap the read-modify-write in a transaction and use &lt;code&gt;select_for_update()&lt;/code&gt; to lock the rows, so two requests can't both read the old value and overwrite each other — a race condition. For "create only once", a unique constraint plus &lt;code&gt;get_or_create&lt;/code&gt; is often simpler than locking.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; awareness of concurrency, not just single-request thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;The difference between a pass and a fail on Django questions is showing a mental model. Say what the framework does and &lt;em&gt;why&lt;/em&gt;, name the one trade-off, and stop. If you're unsure of an exact API, describe the mechanism instead of freezing on the name. If you tend to blank under pressure, that's a separate, fixable skill.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article4" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt;, where you rehearse questions like these on a timer and an AI scores every answer 1–10 with feedback. Free to try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>interview</category>
      <category>beginners</category>
      <category>python</category>
    </item>
    <item>
      <title>SQL Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Sat, 26 Sep 2026 17:54:21 +0000</pubDate>
      <link>https://dev.to/peakblick/sql-interview-questions-with-model-answers-1kn4</link>
      <guid>https://dev.to/peakblick/sql-interview-questions-with-model-answers-1kn4</guid>
      <description>&lt;p&gt;These are the SQL questions that actually come up in backend, data, and full-stack interviews — grouped by topic, each with a short model answer and a note on what the interviewer is really checking.&lt;/p&gt;

&lt;p&gt;SQL questions reward precision. Interviewers can tell in one sentence whether you understand what a query does or you're pattern-matching keywords. For each answer below, notice the shape of a strong response: name the mechanism, give the one trade-off that matters, then stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core SQL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between &lt;code&gt;WHERE&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;WHERE&lt;/code&gt; filters rows &lt;em&gt;before&lt;/em&gt; they're grouped; &lt;code&gt;HAVING&lt;/code&gt; filters &lt;em&gt;after&lt;/em&gt; aggregation, so it can reference aggregate functions like &lt;code&gt;COUNT(*)&lt;/code&gt; or &lt;code&gt;SUM(x)&lt;/code&gt;. Rule of thumb: filter individual rows with &lt;code&gt;WHERE&lt;/code&gt;, filter groups with &lt;code&gt;HAVING&lt;/code&gt;. Putting a plain row condition in &lt;code&gt;HAVING&lt;/code&gt; still works but is slower, because you aggregate rows you could have dropped earlier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you understand the logical order of a query, not just the syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do aggregate functions handle &lt;code&gt;NULL&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Aggregates like &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt;, &lt;code&gt;MIN&lt;/code&gt; and &lt;code&gt;MAX&lt;/code&gt; ignore &lt;code&gt;NULL&lt;/code&gt;s. The catch is &lt;code&gt;COUNT&lt;/code&gt;: &lt;code&gt;COUNT(*)&lt;/code&gt; counts every row, but &lt;code&gt;COUNT(column)&lt;/code&gt; counts only rows where that column is not null — so &lt;code&gt;AVG(col)&lt;/code&gt; divides by the number of non-null values, a common source of "wrong" averages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; awareness that &lt;code&gt;NULL&lt;/code&gt; means "unknown" and silently changes results.&lt;/p&gt;

&lt;h3&gt;
  
  
  How would you filter rows within a range, or against a list of values?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;BETWEEN&lt;/code&gt; for an inclusive range — &lt;code&gt;WHERE age BETWEEN 18 AND 30&lt;/code&gt; — and &lt;code&gt;IN&lt;/code&gt; for a discrete set — &lt;code&gt;WHERE status IN ('open', 'pending')&lt;/code&gt;. Watch out: &lt;code&gt;NOT IN&lt;/code&gt; with a list containing &lt;code&gt;NULL&lt;/code&gt; returns no rows.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; fluency with everyday filtering, plus the &lt;code&gt;NULL&lt;/code&gt; gotcha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Joins &amp;amp; aggregation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Explain &lt;code&gt;INNER JOIN&lt;/code&gt; vs &lt;code&gt;LEFT JOIN&lt;/code&gt;.
&lt;/h3&gt;

&lt;p&gt;An &lt;code&gt;INNER JOIN&lt;/code&gt; returns only rows that match in both tables. A &lt;code&gt;LEFT JOIN&lt;/code&gt; returns every row from the left table, filling &lt;code&gt;NULL&lt;/code&gt;s where the right has no match. Frequent bug: a &lt;code&gt;WHERE&lt;/code&gt; on the right table's column after a &lt;code&gt;LEFT JOIN&lt;/code&gt; quietly turns it back into an inner join — put that condition in the &lt;code&gt;ON&lt;/code&gt; clause.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you can reason about which rows survive, and spot the left-join trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you combine values from multiple rows into one per group?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;GROUP BY&lt;/code&gt; with a string-aggregation function — &lt;code&gt;STRING_AGG(name, ', ')&lt;/code&gt; in PostgreSQL, &lt;code&gt;GROUP_CONCAT&lt;/code&gt; in MySQL. Every non-aggregated column in the &lt;code&gt;SELECT&lt;/code&gt; must appear in the &lt;code&gt;GROUP BY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that aggregation isn't only &lt;code&gt;COUNT&lt;/code&gt;/&lt;code&gt;SUM&lt;/code&gt;, and the &lt;code&gt;GROUP BY&lt;/code&gt; rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a window function, and when would you use one?
&lt;/h3&gt;

&lt;p&gt;A window function computes across related rows &lt;em&gt;without collapsing them&lt;/em&gt; — you keep every row and add a computed column. Classic uses: a running total (&lt;code&gt;SUM(...) OVER (ORDER BY date)&lt;/code&gt;) or ranking within a group (&lt;code&gt;ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a level above basic aggregation — this often separates mid from senior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a database index, and when would you avoid one?
&lt;/h3&gt;

&lt;p&gt;An index is a separate structure (usually a B-tree) that speeds reads by avoiding a full scan. The trade-off is slower writes and extra storage. Avoid one on a small table, or a column written far more than it's read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you weigh read speed against write cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  A query is slow. How do you approach it?
&lt;/h3&gt;

&lt;p&gt;Measure first — run &lt;code&gt;EXPLAIN&lt;/code&gt; and read the plan. Usual culprits: a sequential scan where an index should be used, a missing index on a join/filter column, or returning more rows than needed. Fix the specific cause, then re-check the plan.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a systematic process, and knowing &lt;code&gt;EXPLAIN&lt;/code&gt; exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  How would you efficiently insert or update a large number of rows?
&lt;/h3&gt;

&lt;p&gt;Batch them into a single transaction instead of one statement per row — that avoids per-row commit overhead. For very large loads, use the bulk path (&lt;code&gt;COPY&lt;/code&gt; in PostgreSQL, &lt;code&gt;LOAD DATA&lt;/code&gt; in MySQL), upsert with &lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt;, and consider dropping non-critical indexes during the load.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you think about round-trips and transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design &amp;amp; transactions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does ACID mean?
&lt;/h3&gt;

&lt;p&gt;Atomicity (all-or-nothing), Consistency (valid state to valid state), Isolation (concurrent transactions don't see each other's half-finished work), Durability (committed data survives a crash). Isolation comes up most in practice — it's why you wrap a money transfer in a transaction.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; connecting the acronym to a real reason you'd use a transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is normalization, and when would you denormalize?
&lt;/h3&gt;

&lt;p&gt;Normalization stores each fact once, avoiding update anomalies. You denormalize (deliberately duplicating data) when read performance matters more than write simplicity — a reporting table or a cached count. The trade-off is keeping the duplicate in sync.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you see normalization as a trade-off, not a rule.&lt;br&gt;
The gap between a pass and a fail on SQL questions is rarely the facts — it's precision. State what the query does, name the one trade-off or gotcha that matters, and stop. If you blank under pressure, that's a separate, fixable skill.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article3" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt;, where you practice questions like these on a timer and an AI scores every answer 1–10 with feedback. Free to try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>beginners</category>
      <category>interview</category>
      <category>database</category>
    </item>
    <item>
      <title>Python Backend Interview Questions (With Model Answers)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Wed, 23 Sep 2026 12:08:14 +0000</pubDate>
      <link>https://dev.to/peakblick/python-backend-interview-questions-with-model-answers-ie</link>
      <guid>https://dev.to/peakblick/python-backend-interview-questions-with-model-answers-ie</guid>
      <description>&lt;p&gt;These are the questions that actually come up for mid-level Python backend roles — grouped by topic, each with a short model answer and a note on what the interviewer is really testing.&lt;/p&gt;

&lt;p&gt;Memorising answers won't get you the offer; interviewers can tell. Use these to check your understanding and, more importantly, to rehearse saying them out loud. For each one, notice the pattern of a strong answer: state the mechanism, give the trade-off, then stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python core
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a decorator, and when would you use one?
&lt;/h3&gt;

&lt;p&gt;A decorator is a callable that takes a function and returns a new function wrapping it, so you can add behaviour without changing the original body. Common uses: caching, access checks, logging, timing, retries. In practice you write &lt;code&gt;@decorator&lt;/code&gt; above the function; underneath it's just &lt;code&gt;fn = decorator(fn)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you understand functions as first-class objects — not just the syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the GIL and when does it actually matter?
&lt;/h3&gt;

&lt;p&gt;The Global Interpreter Lock lets only one thread execute Python bytecode at a time, so threads don't give you true parallelism for CPU-bound work. It rarely hurts I/O-bound work (the lock is released during I/O), so threads are still fine for network calls. For CPU-bound parallelism, use &lt;code&gt;multiprocessing&lt;/code&gt; or a native/extension path.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you know when threads help (I/O) versus when to reach for processes (CPU).&lt;/p&gt;

&lt;h3&gt;
  
  
  Generator vs list — why use &lt;code&gt;yield&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;A list holds all items in memory at once; a generator produces them lazily, one at a time, so it uses near-constant memory and can stream huge or infinite sequences. Trade-off: you can only iterate a generator once, and you can't index it. Use generators for large pipelines, lists when you need random access or to reuse the data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; memory awareness and knowing the trade-off, not just the definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why are mutable default arguments dangerous?
&lt;/h3&gt;

&lt;p&gt;A default like &lt;code&gt;def f(x, items=[])&lt;/code&gt; is evaluated once at definition time, so the same list is shared across every call — mutating it leaks state between calls. The fix is &lt;code&gt;items=None&lt;/code&gt; then &lt;code&gt;items = items or []&lt;/code&gt; inside the function.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a classic gotcha that shows you understand when Python evaluates defaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Django &amp;amp; frameworks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;select_related&lt;/code&gt; vs &lt;code&gt;prefetch_related&lt;/code&gt; (and the N+1 problem)
&lt;/h3&gt;

&lt;p&gt;The N+1 problem is when you load a list, then hit the DB once per item for a related object — 1 + N queries. &lt;code&gt;select_related&lt;/code&gt; fixes it for foreign-key / one-to-one by doing a SQL JOIN (one query). &lt;code&gt;prefetch_related&lt;/code&gt; handles many-to-many / reverse FKs with a second query that Python joins in memory. Reach for them whenever you loop over a queryset touching related data.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; whether you can spot and fix the most common ORM performance bug.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when a request hits your Django app?
&lt;/h3&gt;

&lt;p&gt;The WSGI/ASGI server hands the request to Django, which runs it through the middleware stack, resolves the URL to a view, the view does its work (often via the ORM), returns a response, and middleware runs again on the way out. Naming middleware, URL resolution, view, and response is enough to show you understand the flow.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a mental model of the framework, not memorised internals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Databases &amp;amp; APIs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a database index, and when would you avoid one?
&lt;/h3&gt;

&lt;p&gt;An index is a separate structure (usually a B-tree) that speeds up reads by avoiding a full table scan. The trade-off is slower writes and extra storage, since every insert/update maintains the index. Avoid one on a small table or on a column that's written far more than it's read. Short version: index for read-heavy lookups, skip it when writes dominate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; that you weigh read speed against write cost, not just "indexes make things fast".&lt;/p&gt;

&lt;h3&gt;
  
  
  What does idempotency mean in a REST API?
&lt;/h3&gt;

&lt;p&gt;An idempotent request produces the same result no matter how many times it's sent. &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; should be idempotent; &lt;code&gt;POST&lt;/code&gt; usually isn't. It matters for retries — if a client retries after a timeout, an idempotent endpoint won't create duplicates. For non-idempotent creates, an idempotency key is the common fix.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; API design maturity and thinking about failure/retries.&lt;/p&gt;

&lt;h3&gt;
  
  
  An endpoint is slow. How do you approach it?
&lt;/h3&gt;

&lt;p&gt;Measure first — find whether it's the DB, the code, or an external call. The usual culprits for a Python backend: N+1 queries (fix with &lt;code&gt;select_related&lt;/code&gt;/&lt;code&gt;prefetch_related&lt;/code&gt;), a missing index, or fetching more data than needed. Then consider caching the result if it's read-heavy and can tolerate slight staleness. Name the order — measure, fix the query, then cache — rather than jumping to "add Redis".&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What they're testing:&lt;/em&gt; a systematic debugging process, not a memorised list of fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually answer these
&lt;/h2&gt;

&lt;p&gt;The difference between a pass and a fail here is rarely the facts — it's delivery. Open with the mechanism, name the trade-off out loud, and stop when you've answered. If you don't know something, say what you'd expect and why instead of bluffing. If you tend to blank under pressure, that's a separate, fixable skill.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article2" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt;, where you rehearse questions like these on a timer and an AI scores every answer 1–10 with feedback. Free to try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>interview</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Why You Freeze in Technical Interviews (and the Drill That Fixes It)</title>
      <dc:creator>Peakblick</dc:creator>
      <pubDate>Mon, 21 Sep 2026 18:41:40 +0000</pubDate>
      <link>https://dev.to/peakblick/why-you-freeze-in-technical-interviews-and-the-drill-that-fixes-it-4kn3</link>
      <guid>https://dev.to/peakblick/why-you-freeze-in-technical-interviews-and-the-drill-that-fixes-it-4kn3</guid>
      <description>&lt;p&gt;If you go blank in interviews even though you know the material, the problem isn't knowledge — it's that you've never practiced &lt;em&gt;performing&lt;/em&gt; under a clock while talking. That's a separate skill, and it's fixable with reps.&lt;/p&gt;

&lt;p&gt;You've solved the problems. You've read the docs. Then the interviewer asks something you'd nail on your own machine, and your mind goes empty. This is one of the most common — and most misunderstood — reasons strong engineers fail interviews. The good news: the fix has nothing to do with studying more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What freezing actually is
&lt;/h2&gt;

&lt;p&gt;Solving a problem alone and answering it out loud in front of a stranger, on a timer, are two different skills. When you code alone, your working memory is free to think. In an interview, a big chunk of that memory is spent on things that have nothing to do with the problem: watching the clock, worrying about how you sound, wondering what they're thinking. There's less capacity left for the actual answer — so you blank.&lt;/p&gt;

&lt;p&gt;That's why the freeze feels so unfair. You &lt;em&gt;do&lt;/em&gt; know it. You've just never trained your brain to retrieve it while under that specific load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why more studying doesn't help
&lt;/h2&gt;

&lt;p&gt;Grinding more problems trains the wrong muscle. It makes you better at solving silently — which you're already good at. It does nothing for the thing that's actually failing: retrieving and explaining under pressure. You can double your problem count and still freeze, because you never rehearsed the conditions of the interview itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20-minute drill that fixes it
&lt;/h2&gt;

&lt;p&gt;Recreate the pressure, on purpose, until it stops being novel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Talk the entire time.&lt;/strong&gt; Narrate your thinking out loud, even alone. It feels deeply unnatural at first — that discomfort is exactly the skill you're building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a real clock on it.&lt;/strong&gt; 20–30 minutes, no pausing to look things up. The pressure is the point; that's the variable you're bad at, not the topic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record it or get it scored, then review.&lt;/strong&gt; Watch or read it back. You'll immediately hear the rambling, the skipped trade-offs, the "umm" spirals you can't feel in the moment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do this three or four times and the interview stops feeling like an ambush. The retrieval-under-pressure path is now worn in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to fix when you review
&lt;/h2&gt;

&lt;p&gt;From scoring hundreds of answers, the same handful of habits separate a "pass" from a "fail":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structure before detail.&lt;/strong&gt; Open with the shape of your answer — "there are basically three approaches; I'd go with X because…" — then fill in. Interviewers grade signal, and structure is signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name the trade-off out loud.&lt;/strong&gt; "This is O(n) time but costs memory; if the input's bounded I'd do the other one." Saying the trade-off scores higher than a correct answer with no reasoning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admit the unknown.&lt;/strong&gt; "I haven't used that directly, but based on X I'd expect Y" beats a confident wrong answer every time. Bluffing is the fastest fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer the question that was asked.&lt;/strong&gt; Restate it in one line before you start — a huge share of low scores are just off-target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Land the plane.&lt;/strong&gt; End with a one-sentence conclusion instead of trailing off. A clear close changes how the whole answer is remembered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Good vs. weak answer, same question
&lt;/h2&gt;

&lt;p&gt;Question: &lt;em&gt;"What's a database index and when would you avoid one?"&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Weak:&lt;/strong&gt; "An index makes queries faster because it's like a lookup table and the database uses it to find rows quicker so reads are faster…" (correct-ish, but no structure, no trade-off, no close.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strong:&lt;/strong&gt; "An index is a separate data structure that speeds up reads by avoiding a full scan — usually a B-tree. The trade-off is slower writes and extra storage, since every insert updates the index. So I'd avoid one on a small table, or a column that's written far more than it's read. Short version: index for read-heavy lookups, skip it when writes dominate." (Structure → trade-off → when-not → close.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same knowledge. The second one just &lt;em&gt;performs&lt;/em&gt; it — which is the exact skill the drill builds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I built &lt;a href="https://peakblick.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=article1" rel="noopener noreferrer"&gt;Peakblick&lt;/a&gt; to run this drill without a partner: it interviews you for a specific role, on a timer, and scores every answer 1–10 with feedback. Free to try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>career</category>
      <category>interview</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
