# What's the average total time for a game of bullet chess?

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?

• I think combing the average of the last 10000 or so bullet games on lichess would give a pretty good estimate. Commented Feb 8, 2022 at 4:31
• @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 Commented Feb 8, 2022 at 12:18

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:

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)

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)))
``````
• This is great. Just wanted to check if it includes resignations? Commented Feb 10, 2022 at 20:25
• Yes: mates, stalemates, draws, resignations, and loss on time are all counted. I can rerun it with a different filter if you like. Commented Feb 10, 2022 at 20:38
• that’s perfect just wanted to confirm. Commented Feb 10, 2022 at 20:41
• 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. Commented Feb 11, 2022 at 13:38
• Adding for completeness: someone mentioned in another forum "A berzeked game still has [TimeControl "60+0"] , so they are probably included ...You would need to look at the move time in the PGN's to tell if a player zerked. (Now, I don't think this would really change the numbers that much. At most a few seconds? Guessing here thru, but from all rated games, not that many are zerked)" Commented Feb 11, 2022 at 14:07

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.

## 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
``````

• How do premoves generate more time on the clock? Commented Feb 16, 2022 at 20:53
• 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. Commented Feb 16, 2022 at 21:00
• @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! Commented Feb 16, 2022 at 21:06
• @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. Commented Feb 16, 2022 at 22:20
• 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. Commented Feb 19, 2022 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"]
``````

Elapse time can be calculated using the EndTime and StartTime tag values.

#### Top bullet players

``````['Alexander_Zubov', 'AnishGiri', 'Chefshouse',
'nihalsarin', 'NikoTheodorou', 'Oleksandr_Bortnyk',
'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
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
``````