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?

  • 6
    I think combing the average of the last 10000 or so bullet games on lichess would give a pretty good estimate. Feb 8 at 4:31
  • 4
    @RewanDemontay thanks for the great suggestion. I checked out the lichess API and couldn't find an easy way to get the last 10k bullet games. Sure enough, I joined the lichess discord (api-bots-boards channel) and asked, and within 5 seconds flat Thibault himself replied, who said it's not possible via API, but you can download the entire month's database). Unfortunately it's about 30GB - too big for me to download right now, but if anyone's got the bandwidth, here's the link. Perhaps they could grab the last 10k bullet games and pastebin them for us all to use. database.lichess.org
    – stevec
    Feb 8 at 12:18

3 Answers 3


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.


  • 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):

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

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:

enter image description here

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'):
  with open(os.path.join(os.getcwd(), filename), 'r') as f:
    line = f.readline()


      # "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]

        ignore = 1

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

      # ignore "Abandoned", "Unterminated", "Rules infraction"
      # and any other nonstandard endings.
        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)
                dur = s1+s2 - t0

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

                if(s1==60 and s2==60):
      line = f.readline()


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)))
  • 1
    This is great. Just wanted to check if it includes resignations?
    – stevec
    Feb 10 at 20:25
  • 4
    Yes: mates, stalemates, draws, resignations, and loss on time are all counted. I can rerun it with a different filter if you like. Feb 10 at 20:38
  • 2
    that’s perfect just wanted to confirm.
    – stevec
    Feb 10 at 20:41
  • 3
    @L.ScottJohnson I believe it would additionally helpful to provide details on how you obtained and sorted the data for future seekers Feb 11 at 3:18
  • 1
    No sorting: I just look for time control of 60+0 (it's given in seconds) and exclude certain cases like Rules Violations then look at the last two %clk values. I note that if anyone "gives time", then this clk value will throw off the calculations (e.g., in some cases, a player ends with more than 60 seconds left on their clock, which the calculation will see as the game taking negative time: lichess.org/HySUH6IK ). I'll see about adding code to check that the clocks are at least non-increasing. Feb 11 at 13:38

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.

enter image description here

Resigned games

It peaks close to 100s.

enter image description here


enter image description here

Time forfeit

enter image description here


enter image description here


enter image description here

Overall result by game termination

enter image description here

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

enter image description here

  • 1
    How do premoves generate more time on the clock? Feb 16 at 20:53
  • 1
    I wonder if that has to do with the way the server accounts for latency. I've wondered the same thing while playing on lichess -- games that are both down to where each player has 1 to 2 seconds left seem to go a lot longer than the 2-4 seconds indicated by the clock. Feb 16 at 21:00
  • 1
    @L.ScottJohnson Here's an example where I believe that happens. This is an 8s/0 game on both sides, and starts at the 38:44 mark. At the end of the game the players have a combined 3.9 seconds left, which is at the 39:07 mark. That means 12.1 seconds of in game time elapsed, but 23 seconds of real time! Feb 16 at 21:06
  • 4
    @L.ScottJohnson Probably the question is formulated under the assumption that clock time and real time are one and the same. Either way, both answers are interesting. Feb 16 at 22:20
  • 1
    Another factor, perhaps a bigger factor, in the discrepancy is that createdAt is not the time the game started. The added time from those gaps (between game creation and game start) likely dwarfs the accumulated from pre-moves latency. As an extreme example, the 1+0 game I tested with, which ended after 3 seconds, shows 40 minutes and 20 seconds elapsed from creation to last move. Feb 19 at 16:12

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


datasets: 19411
mean: 108
stdev: 35

Distribution plot

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.