user18029: Bullet chess gives each player 1 minute to make all their moves - if that minute passes and they haven't checkmated their opponent, they lose (or if insufficient material, a draw).
Since chess has 2 players, the longest a bullet chess game can go for is approximately 2 minutes (or slightly under because it's not possible for both clocks to count down to exactly zero at the same time).
But many bullet games are much shorter than 2 minutes, for example in games where a checkmate or resignation happens very early in the game (some last only a few seconds).
So what is the average duration of a game of bullet chess?

user30545: I tried to get some data from chess.com using their published api. I use the game archive endpoint to get the data, and use the leaderboard endpoint to get the top 16 players in bullet.
Typical game header in the pgn from pgn key of the returned request response.
[Event "Live Chess"]
[Site "Chess.com"]
[Date "2020.09.07"]
[Round "-"]
[White "DexyDex"]
[Black "Orange_Ghost"]
[Result "1-0"]
[CurrentPosition "1Q3B2/1R6/5p1k/3q1P1p/1P1N2pP/2P1r3/6PK/8 b - -"]
[Timezone "UTC"]
[ECO "C64"]
[ECOUrl "https://www.chess.com/openings/Ruy-Lopez-Opening-Classical-Defense-4.O-O-d6"]
[UTCDate "2020.09.07"]
[UTCTime "21:54:44"]
[WhiteElo "2839"]
[BlackElo "2849"]
[TimeControl "60"]
[Termination "DexyDex won by checkmate"]
[StartTime "21:54:44"]
[EndDate "2020.09.07"]
[EndTime "21:56:43"]
[Link "https://www.chess.com/game/live/5413100443"]

Elapse time can be calculated using the EndTime and StartTime tag values.
Top bullet players
['Alexander_Zubov', 'AnishGiri', 'Chefshouse',
'DanielNaroditsky', 'GMWSO', 'Hikaru', 'Indianlad',
'nihalsarin', 'NikoTheodorou', 'Oleksandr_Bortnyk',
'Orange_Ghost', 'RaunakSadhwani2005', 'SmurferHe',
'spicycaterpillar', 'wonderfultime', 'Zhigalko_Sergei']

Sample datasets
   elapse_sec  tc                            termi                                         link
0         142  60             chessawp won on time   https://www.chess.com/game/live/3359644035
1         123  60             chessawp won on time   https://www.chess.com/game/live/3359654153
2         105  60  TrimitziosP7 won by resignation  https://www.chess.com/game/live/38857280443
3         157  60         TrimitziosP7 won on time  https://www.chess.com/game/live/38857733457
4         111  60     denizozen won by resignation  https://www.chess.com/game/live/38866804411
       elapse_sec  tc                               termi                                        link
19406         144  60         Zhigalko_Sergei won on time  https://www.chess.com/game/live/2793173203
19407          52  60  Zhigalko_Sergei won by resignation  https://www.chess.com/game/live/2793177225
19408         131  60    Zhigalko_Sergei won by checkmate  https://www.chess.com/game/live/2831119397
19409         109  60          Kanallija won by checkmate  https://www.chess.com/game/live/2831121747
19410         120  60        Kanallija won by resignation  https://www.chess.com/game/live/2831123668

Figures
datasets: 19411
mean: 108
stdev: 35

Distribution plot


user30545: Collected data from the top 100 bullet players in Lichess using the berserk module from lichess api.
The function client.games.export_by_player() can give info about the games played by a player. This includes createdAt and lastMoveAt timestamps values from which we can calculate the duration of the game.
Be noted that extracting game time duration through game clk info may not be appropriate because most of these players are pre-moving. Even if their clock is at 1s some players can still make some moves with their clock still at 1s.
I. Bullet games at TC 60+0
Some figures
Bullet games from top 100 bullet Lichess players, each player has
around 200 games at tc 60+0 less duplicates.
num data: 18125
min   (s): 21
max   (s): 200
mean  (s): 103
stdev (s): 32

Overall distribution
There are games that are more than 120s due to pre-moves.

Resigned games
It peaks close to 100s.

Checkmates

Time forfeit

Draws

Stalemate

Overall result by game termination

II. Bullet games at TC 30+0
Bullet games from top 100 bullet Lichess players at TC 30+0
num data: 6963
min   (s): 21
max   (s): 120
mean  (s): 63
stdev (s): 17



user26472: Per the suggestion by Rewan in the comments, I pulled the January 2022 log of games from lichess and looked at all the 1+0 games that finished with "Normal" or "Time forfeit" results with both players making at least one move each.
The code I used is included below.
Notes:

Elapsed time is calculated as the sum of the starting clocks for each player minus the sum of the ending clocks for each player. Starting clocks (rather than a constant 120s) are used to handle the case when a player berserks.

I exclude cases where players gave their opponents more time (since this makes calculating elapsed time impossible from the PGN data given). At least, I try to; there are some cases that will be undetectable, such as giving time and the opponent using up all of that time prior to making their next more.

I do not use wall clock game creation time and game end time so as to avoid having the calculation artificially inflated with the extra non-game time introduced by the delay between game creation and game start and lag/ping latency.


Number of games:  27,398,824
Average time: 93.51 seconds
Median time: 104 seconds
Using just the games in which neither player berserks:
Number of games:  27,130,482
Average time: 93.77 seconds
Median time: 105 seconds
Histogram of durations in that second set (no berserks):

Note the blip at t=60s. This duration is over-represented due to the games that have exactly two moves with white timing out. These stem, I believe, from challenges in which the challenged player does not immediately respond, and the challenger (playing white) doesn't notice when the challenge is ultimately accepted.
Also note that the duration of the game is at least somewhat dependent on the strengths of the players. Here's a graph of the durations for games in which both players were about the same strength (Elo within 100 of each other) plotted against the players' average rating:

I'm guessing the sudden dip at the end is an artifact stemming from the lower game count there, although perhaps to reach that level, premoves really become much more necessary.
python3 code:
import glob
import os
import re
import statistics

def seconds(time_str):
    """convert h:mm:ss to total seconds"""
    h, m, s = time_str.split(':')
    return int(h) * 3600 + int(m) * 60 + int(s)

# function to verify each player's clock is monotone
# decreasing (if a player is given time by their 
# opponent, then the calculation of total game time
# is not possible given only the PGN data supplied)
def non_increasing(l):
    return all(x>=y for x, y in zip(l, l[1:]))

clock = re.compile(r'(?:\[%clk )(\d:\d\d:\d\d)')

ignore = 0
bullet = 0
time = 0
gameid = ""
alltimes = []
sanetimes = []

statfile = open('stats.txt','w')
statfile.write("%8s %3s %3s %5s %5s\n"
  % ("GameID", "Dur", "nPly", "Wclk", "Bclk"))

for filename in glob.glob('*.pgn'):
  print(filename)
  with open(os.path.join(os.getcwd(), filename), 'r') as f:
    line = f.readline()

    while(line):

      # "Site" is the second line of the each game, so it
      # is a good place to serve up the reset
      if(line.find('[Site ')>=0):
        ignore = 0
        timeout = 0
        bullet = 0
        s = line.split('"')
        s = s[1].split('/')
        gameid = s[-1]

      if(line.find('[Variant')>=0):
        ignore = 1

      if(line.find('[TimeControl')>=0):
        s = line.split('"')
        if(s[1] == '60+0'):
          bullet = 1

      # ignore "Abandoned", "Unterminated", "Rules infraction"
      # and any other nonstandard endings.
      if(line.find('[Termination')>=0):
        s = line.split('"')
        if(s[1] == "Time forfeit"):
          timeout = 1
        elif(s[1] != "Normal"):
          ignore = 1

      if(line[0] == '1'):
        if((bullet == 1) and (ignore == 0)):
          times = clock.findall(line)
          if(len(times) >= 2):
            secs = [ seconds(item) for item in times ]
            p1 = secs[::2]
            p2 = secs[1::2]

            # record the first clock value for each player.
            # it should be 60, but if the player berserked
            # then it will be 30. Whichever, we use this
            # value to determine the elapsed time for that
            # player's clock at the end.
            s1 = p1[0]
            s2 = p2[0]

            if(s1<=60 and s2<=60 and non_increasing(p1)
                and non_increasing(p2)):
              t0 = secs[-1]
              if(timeout == 0):
                t1 = secs[-2]
                dur = s1+s2 - (t0 + t1)
              else:
                dur = s1+s2 - t0

              if(dur >= 0 and dur <=120):
                statfile.write("%8s %3d %3d %5d %5d\n"
                  % (gameid, dur, len(secs), s1, s2))

                alltimes.append(dur)
                if(s1==60 and s2==60):
                  sanetimes.append(dur)
        
      line = f.readline()

f.close()
statfile.close()

print("Number of games: ", len(alltimes))
print("Average time: %s seconds"
  % ( statistics.mean(alltimes)))
print("Median time: %s seconds"
  % ( statistics.median(alltimes)))

print("Number of non-berserked games: ", len(sanetimes))
print("Average time: %s seconds"
  % ( statistics.mean(sanetimes)))
print("Median time: %s seconds"
  % ( statistics.median(sanetimes)))


18029: <p>Bullet chess gives each player 1 minute to make all their moves - if that minute passes and they haven't checkmated their opponent, they lose (or if insufficient material, a draw).</p>
<p>Since chess has 2 players, the longest a bullet chess game can go for is approximately 2 minutes (or slightly under because it's not possible for both clocks to count down to exactly zero at the same time).</p>
<p>But many bullet games are much shorter than 2 minutes, for example in games where a checkmate or resignation happens very early in the game (some last only a few seconds).</p>
<p>So what is the <em>average</em> duration of a game of bullet chess?</p>
 30545: <p>I tried to get some data from chess.com using their <a href="https://www.chess.com/news/view/published-data-api" rel="nofollow noreferrer">published api</a>. I use the <a href="https://www.chess.com/news/view/published-data-api#pubapi-endpoint-games-archive-list" rel="nofollow noreferrer">game archive</a> endpoint to get the data, and use the <a href="https://www.chess.com/news/view/published-data-api#pubapi-leaderboards" rel="nofollow noreferrer">leaderboard</a> endpoint to get the top 16 players in bullet.</p>
<p>Typical game header in the pgn from pgn key of the returned request response.</p>
<pre><code>[Event &quot;Live Chess&quot;]
[Site &quot;Chess.com&quot;]
[Date &quot;2020.09.07&quot;]
[Round &quot;-&quot;]
[White &quot;DexyDex&quot;]
[Black &quot;Orange_Ghost&quot;]
[Result &quot;1-0&quot;]
[CurrentPosition &quot;1Q3B2/1R6/5p1k/3q1P1p/1P1N2pP/2P1r3/6PK/8 b - -&quot;]
[Timezone &quot;UTC&quot;]
[ECO &quot;C64&quot;]
[ECOUrl &quot;https://www.chess.com/openings/Ruy-Lopez-Opening-Classical-Defense-4.O-O-d6&quot;]
[UTCDate &quot;2020.09.07&quot;]
[UTCTime &quot;21:54:44&quot;]
[WhiteElo &quot;2839&quot;]
[BlackElo &quot;2849&quot;]
[TimeControl &quot;60&quot;]
[Termination &quot;DexyDex won by checkmate&quot;]
[StartTime &quot;21:54:44&quot;]
[EndDate &quot;2020.09.07&quot;]
[EndTime &quot;21:56:43&quot;]
[Link &quot;https://www.chess.com/game/live/5413100443&quot;]
</code></pre>
<p>Elapse time can be calculated using the EndTime and StartTime tag values.</p>
<h4>Top bullet players</h4>
<pre><code>['Alexander_Zubov', 'AnishGiri', 'Chefshouse',
'DanielNaroditsky', 'GMWSO', 'Hikaru', 'Indianlad',
'nihalsarin', 'NikoTheodorou', 'Oleksandr_Bortnyk',
'Orange_Ghost', 'RaunakSadhwani2005', 'SmurferHe',
'spicycaterpillar', 'wonderfultime', 'Zhigalko_Sergei']
</code></pre>
<h4>Sample datasets</h4>
<pre><code>   elapse_sec  tc                            termi                                         link
0         142  60             chessawp won on time   https://www.chess.com/game/live/3359644035
1         123  60             chessawp won on time   https://www.chess.com/game/live/3359654153
2         105  60  TrimitziosP7 won by resignation  https://www.chess.com/game/live/38857280443
3         157  60         TrimitziosP7 won on time  https://www.chess.com/game/live/38857733457
4         111  60     denizozen won by resignation  https://www.chess.com/game/live/38866804411
       elapse_sec  tc                               termi                                        link
19406         144  60         Zhigalko_Sergei won on time  https://www.chess.com/game/live/2793173203
19407          52  60  Zhigalko_Sergei won by resignation  https://www.chess.com/game/live/2793177225
19408         131  60    Zhigalko_Sergei won by checkmate  https://www.chess.com/game/live/2831119397
19409         109  60          Kanallija won by checkmate  https://www.chess.com/game/live/2831121747
19410         120  60        Kanallija won by resignation  https://www.chess.com/game/live/2831123668
</code></pre>
<h4>Figures</h4>
<pre><code>datasets: 19411
mean: 108
stdev: 35
</code></pre>
<h4>Distribution plot</h4>
<p><a href="https://i.stack.imgur.com/AnuCB.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/AnuCB.png" alt="enter image description here" /></a></p>
 30545: <p>Collected data from the <a href="https://lichess.org/player/top/200/bullet" rel="nofollow noreferrer">top 100 bullet players in Lichess</a> using the <a href="https://berserk.readthedocs.io/en/master/usage.html#by-player" rel="nofollow noreferrer">berserk</a> module from <a href="https://lichess.org/api" rel="nofollow noreferrer">lichess api</a>.</p>
<p>The function <code>client.games.export_by_player()</code> can give info about the games played by a player. This includes <code>createdAt</code> and <code>lastMoveAt</code> timestamps values from which we can calculate the duration of the game.</p>
<p>Be noted that extracting game time duration through game clk info may not be appropriate because most of these players are pre-moving. Even if their clock is at 1s some players can still make some moves with their clock still at 1s.</p>
<h2>I. Bullet games at TC 60+0</h2>
<h4>Some figures</h4>
<p>Bullet games from top 100 bullet Lichess players, each player has
around 200 games at tc 60+0 less duplicates.</p>
<pre><code>num data: 18125
min   (s): 21
max   (s): 200
mean  (s): 103
stdev (s): 32
</code></pre>
<h4>Overall distribution</h4>
<p>There are games that are more than 120s due to pre-moves.</p>
<p><a href="https://i.stack.imgur.com/eoWSw.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/eoWSw.png" alt="enter image description here" /></a></p>
<h4>Resigned games</h4>
<p>It peaks close to 100s.</p>
<p><a href="https://i.stack.imgur.com/qOiVL.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/qOiVL.png" alt="enter image description here" /></a></p>
<h4>Checkmates</h4>
<p><a href="https://i.stack.imgur.com/NeMes.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/NeMes.png" alt="enter image description here" /></a></p>
<h4>Time forfeit</h4>
<p><a href="https://i.stack.imgur.com/3NSnL.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/3NSnL.png" alt="enter image description here" /></a></p>
<h4>Draws</h4>
<p><a href="https://i.stack.imgur.com/w3rco.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/w3rco.png" alt="enter image description here" /></a></p>
<h4>Stalemate</h4>
<p><a href="https://i.stack.imgur.com/TpULn.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/TpULn.png" alt="enter image description here" /></a></p>
<h4>Overall result by game termination</h4>
<p><a href="https://i.stack.imgur.com/c6qvQ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/c6qvQ.png" alt="enter image description here" /></a></p>
<h2>II. Bullet games at TC 30+0</h2>
<p>Bullet games from top 100 bullet Lichess players at TC 30+0</p>
<pre><code>num data: 6963
min   (s): 21
max   (s): 120
mean  (s): 63
stdev (s): 17
</code></pre>
<p><a href="https://i.stack.imgur.com/YudrG.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/YudrG.png" alt="enter image description here" /></a></p>
 26472: <p>Per the suggestion by Rewan in the comments, I pulled the January 2022 log of games from lichess and looked at all the 1+0 games that finished with &quot;Normal&quot; or &quot;Time forfeit&quot; results with both players making at least one move each.</p>
<p>The code I used is included below.</p>
<p>Notes:</p>
<ul>
<li><p>Elapsed time is calculated as the sum of the starting clocks for each player minus the sum of the ending clocks for each player. Starting clocks (rather than a constant 120s) are used to handle the case when a player berserks.</p>
</li>
<li><p>I exclude cases where players gave their opponents more time (since this makes calculating elapsed time impossible from the PGN data given). At least, I try to; there are some cases that will be undetectable, such as giving time and the opponent using up all of that time prior to making their next more.</p>
</li>
<li><p>I do not use wall clock game creation time and game end time so as to avoid having the calculation artificially inflated with the extra non-game time introduced by the <a href="https://lichess.org/forum/lichess-feedback/query-how-is-time-spent-playing-on-profile-calculated#10" rel="nofollow noreferrer">delay between game creation and game start</a> and <a href="https://lichess.org/forum/general-chess-discussion/fast-blitzbullet-not-fair-due-to-time-adjustment-from-server" rel="nofollow noreferrer">lag/ping latency</a>.</p>
</li>
</ul>
<p>Number of games:  27,398,824<br />
Average time: 93.51 seconds<br />
Median time: 104 seconds</p>
<p>Using just the games in which neither player berserks:</p>
<p>Number of games:  27,130,482<br />
Average time: 93.77 seconds<br />
Median time: 105 seconds</p>
<p>Histogram of durations in that second set (no berserks):</p>
<p><a href="https://i.stack.imgur.com/mI3X4.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/mI3X4.png" alt="Histogram of values with duration of games as the x-axis, showing a slow increase from 0 seconds to 80 seconds transitioning to a rapid growth from 80 seconds to 120 seconds" /></a></p>
<p>Note the blip at t=60s. This duration is over-represented due to the games that have exactly two moves with white timing out. These stem, I believe, from challenges in which the challenged player does not immediately respond, and the challenger (playing white) doesn't notice when the challenge is ultimately accepted.</p>
<p>Also note that the duration of the game is at least somewhat dependent on the strengths of the players. Here's a graph of the durations for games in which both players were about the same strength (Elo within 100 of each other) plotted against the players' average rating:</p>
<p><a href="https://i.stack.imgur.com/TcpHL.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/TcpHL.png" alt="enter image description here" /></a></p>
<p>I'm guessing the sudden dip at the end is an artifact stemming from the lower game count there, although perhaps to reach that level, premoves really become much more necessary.</p>
<p>python3 code:</p>
<pre><code>import glob
import os
import re
import statistics

def seconds(time_str):
    &quot;&quot;&quot;convert h:mm:ss to total seconds&quot;&quot;&quot;
    h, m, s = time_str.split(':')
    return int(h) * 3600 + int(m) * 60 + int(s)

# function to verify each player's clock is monotone
# decreasing (if a player is given time by their 
# opponent, then the calculation of total game time
# is not possible given only the PGN data supplied)
def non_increasing(l):
    return all(x&gt;=y for x, y in zip(l, l[1:]))

clock = re.compile(r'(?:\[%clk )(\d:\d\d:\d\d)')

ignore = 0
bullet = 0
time = 0
gameid = &quot;&quot;
alltimes = []
sanetimes = []

statfile = open('stats.txt','w')
statfile.write(&quot;%8s %3s %3s %5s %5s\n&quot;
  % (&quot;GameID&quot;, &quot;Dur&quot;, &quot;nPly&quot;, &quot;Wclk&quot;, &quot;Bclk&quot;))

for filename in glob.glob('*.pgn'):
  print(filename)
  with open(os.path.join(os.getcwd(), filename), 'r') as f:
    line = f.readline()

    while(line):

      # &quot;Site&quot; is the second line of the each game, so it
      # is a good place to serve up the reset
      if(line.find('[Site ')&gt;=0):
        ignore = 0
        timeout = 0
        bullet = 0
        s = line.split('&quot;')
        s = s[1].split('/')
        gameid = s[-1]

      if(line.find('[Variant')&gt;=0):
        ignore = 1

      if(line.find('[TimeControl')&gt;=0):
        s = line.split('&quot;')
        if(s[1] == '60+0'):
          bullet = 1

      # ignore &quot;Abandoned&quot;, &quot;Unterminated&quot;, &quot;Rules infraction&quot;
      # and any other nonstandard endings.
      if(line.find('[Termination')&gt;=0):
        s = line.split('&quot;')
        if(s[1] == &quot;Time forfeit&quot;):
          timeout = 1
        elif(s[1] != &quot;Normal&quot;):
          ignore = 1

      if(line[0] == '1'):
        if((bullet == 1) and (ignore == 0)):
          times = clock.findall(line)
          if(len(times) &gt;= 2):
            secs = [ seconds(item) for item in times ]
            p1 = secs[::2]
            p2 = secs[1::2]

            # record the first clock value for each player.
            # it should be 60, but if the player berserked
            # then it will be 30. Whichever, we use this
            # value to determine the elapsed time for that
            # player's clock at the end.
            s1 = p1[0]
            s2 = p2[0]

            if(s1&lt;=60 and s2&lt;=60 and non_increasing(p1)
                and non_increasing(p2)):
              t0 = secs[-1]
              if(timeout == 0):
                t1 = secs[-2]
                dur = s1+s2 - (t0 + t1)
              else:
                dur = s1+s2 - t0

              if(dur &gt;= 0 and dur &lt;=120):
                statfile.write(&quot;%8s %3d %3d %5d %5d\n&quot;
                  % (gameid, dur, len(secs), s1, s2))

                alltimes.append(dur)
                if(s1==60 and s2==60):
                  sanetimes.append(dur)
        
      line = f.readline()

f.close()
statfile.close()

print(&quot;Number of games: &quot;, len(alltimes))
print(&quot;Average time: %s seconds&quot;
  % ( statistics.mean(alltimes)))
print(&quot;Median time: %s seconds&quot;
  % ( statistics.median(alltimes)))

print(&quot;Number of non-berserked games: &quot;, len(sanetimes))
print(&quot;Average time: %s seconds&quot;
  % ( statistics.mean(sanetimes)))
print(&quot;Median time: %s seconds&quot;
  % ( statistics.median(sanetimes)))
</code></pre>
