Race report: Ironman 70.3 Maine 2025

Race information

  • Name: Ironman 70.3 Maine
  • Date: 27 July 2025
  • Time: 5:05:55

Goals

GoalDescriptionCompleted?
ASub-5No
BRace the hardest I canYes

Splits

SplitTimeAge group rank
Swim0:27:06154
T10:07:49
Bike2:49:0278
T20:03:45
Run1:38:1352

Background

I've been running and cycling semi-seriously for about five years now. Two years ago, I got into triathlon, and since then I've done a couple of sprint- and Olympic-distance races. Maine was my first attempt at a 70.3.

Earlier this year, however, my focus was purely on running. I ran the Cheap Marathon in April, roughly following the Pfitz 18/55 training plan. This worked fantastically well, taking an astounding 31 minutes off my previous marathon PR to complete the race in 3:07. However...

Training

...this left me with only 16 weeks to train for Maine. That's plenty of time for a build off of a solid base, but I'd seriously neglected swimming and cycling during my marathon training cycle. An FTP test early in my 70.3 training, about a month after the marathon, revealed that my FTP had dropped from 287 W the previous fall to 238 W - nearly a 50 W decline! (My swimming performance wasn't impacted nearly as drastically - I'm still not a great swimmer, but my swim fitness was about where it was in the fall.)

Given that Maine has a downriver swim, it was clear that the bike was by far my biggest limiter. On the bright side, though, I didn't have to reach new heights of fitness - I just had to rebuild to where I'd been six months ago. So my focus shifted overwhelmingly to cycling. I dropped from 3 swim workouts per week to 2, using the extra time to bike instead, and maintained my running at around 25 miles per week (down from a consistent 50mpw or so during the marathon build). My focus early on was on completing two high-intensity bike sessions per week - VO2max and threshold at first, then threshold and race pace (zone 3, or about 80% of FTP).

This approach worked! I didn't have time to retest my FTP, but by a few weeks out from the race, Garmin estimated my FTP at around 260 W. This matched my intuitive feeling - workouts based on this FTP felt right. In the last few weeks, I dialed back the cycling intensity a bit and added some running intensity back in.

Five weeks out from Maine, I competed in the Olympic distance of the White Mountains Triathlon as a fitness and equipment check. While the swim and the bike went to plan, a failure to properly hydrate before the race caught up to me on the run; still, I performed well enough to finish second in my age group. I count that as a win. (And at least I didn't crash into a bear.)

Injury

Shortly after that race, I started to notice some pain in my right knee.

At first, I thought this might be caused by a bike fit issue. At White Mountains Tri, I discovered that my armrests were set too far back, causing my knees to hit them on rough terrain; I ended the race with both knees bruised. Maybe the pain was just caused by inflammation from that impact? So I continued training, pushing through the pain, until - the week of the race - I admitted that it wasn't getting better and I needed to do something about it.

I got myself to a PT just a few days before the race, and she confirmed my fears: it was a quadriceps tendinopathy. Still, I wasn't about to give up on my goals. I religiously did the isometric exercises my PT prescribed, and I started using KT tape. Hopefully, that would be enough.

Pre-race

My wife Ally and I made the drive to Augusta on Friday afternoon, arriving at the Airbnb we would share with my parents around 3pm. After I checked in, we spent some time exploring the expo before the race briefing (which mostly covered the same information as the athlete guide.) My previous Ironman experiences were in Lake Placid and Kona (both as a spectator), so inevitably I found the expo underwhelming.

The Kennebec River isn't open to swimmers before race day. Luckily, our Airbnb was on a lake, so I was able to get in a short swim Saturday morning. I then drove back to the race start for a quick bike and run shakeout. With KT tape, my knee seemed to hold up OK even at race pace. I dropped off my bike and spent the rest of the day off my feet, eating carbs.

Race day

Race morning

My alarm went off at 4am. Unlike many athletes (my past self included), I slept well the night before the race; nonetheless, I was a little restless that morning and jumped out of bed, ready to start the race. All my gear was ready to go; I ate a quick breakfast (a bagel with peanut butter) and drank a cup of instant coffee before heading out at 4:30, right on schedule.

It was only a 15-minute drive to transition; with a 6am swim start, I thought this would give me plenty of time. (Can you tell this is my first Ironman-branded race?) Yet parking was already nearly impossible to find; my parents ended up dropping off Ally and me while they continued to look for parking. After we walked to transition and I got my stuff set up, I realized I was running out of time to make the one-mile walk to the swim start (downriver, remember?) and make a much-needed port-a-potty stop. So, morning clothes bag in hand, I made my way to the start. Despite being more rushed than I'd anticipated, I still had almost all my equipment in order; the only oversight was sunscreen, which was still in the car. So I couldn't apply before the race. But I coordinated with Ally to hand me some as I ran into T1.

The walk to the swim start resulted in the first hiccup of the day. I made it with time to spare, but as I rushed over there, my slides gave me a blister on my left pinky toe. Luckily it ended up not bothering me during the race, but it was an ominous start to the day. I was, however, able to get to a port-a-potty with almost no wait (pro tip: those closest to the swim start were the least congested). I handed off my morning clothes bag to a volunteer and lined up for the swim.

Swim

Despite being in a river in Maine, somehow the water was warm enough to not be wetsuit-legal. On top of that, the current was weaker than I expected - enough to propel me swim way faster than I could unassisted, sure, but not quite the massive boost I was promised. I really don't have much to report on the swim, though; I just settled into a rhythm and swam until I reached the end. My time for the swim was 27:06.

T1

The run into T1 was really long - somewhere around 0.4 miles - and uphill. Doing this barefoot was pretty brutal. Once I reached my bike, I fumbled a bit getting my socks on (not something I usually struggle with). This resulted in a T1 time of 7:49, which I'm pretty sure is my worst transition time in any race, period. I guess the benefit of not wearing a wetsuit is that transition would have been even worse.

Bike

The bike course is where things started to go off the rails. (Pun intended.)

Here's a photo of my bike, taken a few weeks before the race:

(I didn't use the disc wheel on race day; it was too finicky to set up, and given the elevation profile it was of marginal benefit. I'll spend more time dialing it in before my next race.)

Note the dual BTA (between the arms) bottles. The bottom bottle cage, from Tririg, was solid; the top one, from Profile Design, was a little weak. I ordered another Tririg cage a few weeks before the race; USPS lost it for a while and it only arrived a few days before, so I didn't get a chance to test it. A different cage model from the one I had (Kappa SL rather than plain Kappa) was on sale, so I ordered that one instead.

I'm sure you've heard the saying "nothing new on race day". Reader, that means nothing whatsoever, no matter how minor.

See, I assumed that a bottle cage from the same manufacturer as a known good one, from the same product line, would perform similarly. I could not have been more wrong. Almost as soon as I left T1, the bottle in that cage ejected. That cage was my most accessible; without thinking, I grabbed my rear bottle and put it where the ejected bottle had been. I quickly realized my mistake, but before I could correct it, that bottle was gone, too. Less than a mile into the bike leg, I was down to one bottle.

OK, I thought to myself - this isn't that bad, I can adapt. This is a one-liter bottle with 120 grams of carbs. I'm carrying three gels, each of which has 30 grams; I thought these would be overkill, but all told, this puts me only a little bit behind. I haven't tried Maurten gels, but I have tried Maurten solids; I can grab one at an aid station, that's 40 grams, maybe I'll be OK! As for hydration, I can just grab a water bottle and put it in my rear cage. Not ideal, sure, but I could adapt. And it was relatively cool out, so I wasn't going to go through my water that quickly.

Once I was out of town, I settled nicely into my target pace of about 200 W - a little more on the climbs, a little (or a lot) less on the descents. The bike course was fairly hilly, at about 3600 feet of elevation gain, but there were no major hills - just constant gentle rollers, up and down, the whole way. At the first aid station, I grabbed a water bottle and, realizing I hadn't practiced using the rear cage in training, had to pull over briefly to put it in. (I had never tried the Mortal Hydration electrolyte drink on offer, and as I hope I've made clear, nothing new on race day.) At the second one, I grabbed a Maurten solid; sadly, it had been cut in half, so it only had half the carbs I'd anticipated.

Around mile 35, disaster struck again. Out of nowhere, I heard a thunk! behind me. Looking back, I saw my entire rear cage assembly - including the rail connecting it to the bike - on the ground. I'd just lost not only the water I was counting on drinking, but also my flat repair kit. So now I just had to hope I didn't flat! (Luckily, I didn't.)

At the third and final aid station, I dumped my last remaining bottle, grabbed another water bottle, and tried to grab another Maurten solid - but this time, I fumbled the handoff and didn't get it. I drank most of the water before finishing the bike, putting me only a little bit behind my fueling and hydration strategy. As I entered T2, I still felt OK. For now.

T2

At least the missing rear cage made the bike easier to rack.

After re-racking my bike, I put on my Vaporflys and grabbed my race belt, four gels, and a bag of salt chews, eating the first gel as I ran out to the run course. I had to stop to re-tie my right shoe after tying it too tightly the first time, which cost me some time. One of my main takeaways from this race is that I need to spend more time practicing my transitions.

Run

This was the part of the race I was most nervous about. I had no doubt I had the fitness for a strong run performance, but I wasn't sure how my knee would hold up. Would I even be able to finish the run?

The course started with a steep downhill before turning onto a trail. Descents are typically what aggravated my knee the most, but it held up OK in that first section. As I settled into roughly marathon pace in the first few miles, some mild knee pain came and went, but it was entirely manageable. I felt strong and was passing runner after runner.

That feeling didn't last as I came into the hilliest part of the race, the last five miles or so. The knee pain was gradually increasing. It would flare up when descending, then reduce when the descent ended, but settle just a little above the previous level - ramping up over time. It never got bad enough to make me stop running, but it was starting to impact my pace.

When I got to the biggest hill of the race, around mile 10, my pace really suffered; I just didn't have it in me to push harder. On the way down, I gritted my teeth through the pain - but I knew at this point I wasn't quitting. Still, only two people passed me the entire run, both of them in the last few miles. My strong running fitness was still paying dividends, even if this wasn't quite the run split I'd trained for.

After cresting one final hill, I crossed the finish line in a time of 5:05:55. More than anything else, I felt relieved that my knee hadn't prevented me from finishing the race.

Post-race

After linking up with my family, I made a beeline for the food tent. Once back at the Airbnb, I devoured even more food. After picking up my bike, I spent the rest of the day on the couch.

Reflections

I could point to a million things that could've gone differently that would have taken me under my five-hour goal. If the current had been stronger. If the swim had been wetsuit-legal. If I hadn't fumbled my transitions. If I hadn't dropped a chain. If my nutrition & hydration strategy on the bike hadn't gone completely out the window thanks to my equipment failures. If I hadn't been injured on race day.

But we don't get to choose what goes wrong in a race. All we can do is adapt and put in our best effort despite what the race throws at us.

So, yeah, there are definitely lessons I've learned, things I'll do differently when training for my next race. I'll use tried and tested bottle cages. I'll practice my transitions. I'll set up my chain catcher properly to prevent dropped chains. Most importantly, of course, I'll focus on healing this Injury and preventing any future ones.

But am I disappointed I missed the sub-5 time I was chasing? Well, to be honest, a little bit, yeah. But I'm not hung up on missing an arbitrary time. I'm proud not only of the time I achieved, given the challenging circumstances - I trained hard and gave everything I had on the course. But I'm perhaps even prouder of how I adapted to and overcame the challenges I faced on race day.

Trying (and failing) to build my own race predictor

One of the core features I originally envisioned for KineticAI was an automated race predictor. My original idea was to essentially re-implement the SuperPower Calculator, using historical training data to calculate an individual running effectiveness value and Riegel exponent. But I also wanted a project to learn more about machine learning, and race prediction seems like a textbook ML model - with enough data about how different aspects of training correlate with race times, it should be possible to train a highly accurate race prediction model.

Data collection

The first problem was finding the data I'd need to train the model. It took some digging, but I managed to find a study investigating the same problem that had publicly shared their data. Once I downloaded the provided XLSX file, converted it to CSV, and decoded the cryptic column names, I was off to the races.

First attempt

My first approach used XGBoost, a popular decision tree-based ML library. To start out, I decided to train it to predict 10k times, and use other race times as model inputs - just as other predictors allow you to use past performance to predict future ones. It was dead simple to set up:

model = xgb.XGBRegressor(
    n_estimators=100,
    learning_rate=0.05,
    max_depth=4,
    min_child_weight=3,
    subsample=0.8,
    colsample_bytree=0.8,
    random_state=42,
    # Enable built-in missing value handling
    missing=np.nan,
    eval_metric=['rmse'],
)

model.fit(
    X_train, y_train,
    eval_set=[(X_train, y_train), (X_test, y_test)],
    verbose=True
)

Evaluated on the test set, this model had a mean absolute error (MAE) of about 190 seconds, or just over 3 minutes. That's ... not great. Could I do better with another approach?

Neural network approach

In the spirit of learning, I next tried building a neural network-based predictor. This model was a little bit more complex, though still nothing crazy:

model = Sequential([

    layers.Input(shape=(input_dim,)),

    layers.Dense(64, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.2),

    layers.Dense(32, activation='relu'),
    layers.BatchNormalization(),
    layers.Dropout(0.1),

    layers.Dense(1)
])

model.compile(
    optimizer=optimizers.Adam(learning_rate=0.001),
    loss='mean_squared_error',
    metrics=['mean_absolute_error']
)

early_stopping = keras.callbacks.EarlyStopping(
    monitor='val_loss',
    patience=10,
    restore_best_weights=True
)

history = model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),
    epochs=1000,
    batch_size=32,
    callbacks=[early_stopping],
    verbose=1
)

This set up a model with:

  • 2 hidden layers
  • Dropout in each layer randomly turning some neurons off to prevent overfitting
  • Early stopping if model performance plateaued

The initial NN-based prediction had an MAE on the training set of 212 seconds (~3.5 minutes), and on the validation set of 189 seconds (~3 minutes). In other words, it performed better in validation than in training! This suggested that it was actually underfitting the data, and that the dropout values I had set were too high - the dropout could be reduced without causing overfitting.

I made a couple of tweaks to the model, one at a time, to try and improve its performance:

  • Reduced dropout
  • Added another hidden layer
  • Dynamically adjusted the learning rate using ReduceLROnPlateau

After making these changes, the MAE was ... virtually identical. Nothing I did - adjusting hyperparameters or switching model architectures entirely - seemed capable of producing a model more accurate than 3 minutes.

Looking at the data I had available, this really shouldn't have been surprising. The dataset provided by the study only had about 2000 entries, about half of which included 10k times - so I only had 1000 data points to train from. Given the number of features in the dataset and the inherent complexity of what determines race performance, it's clear that this simply wasn't enough data.

Learning from failure

This was, unfortunately, the largest (and only) dataset of its kind I was able to find - no other public dataset exists that correlates running training with race performances. As a result, I had no choice but to abandon the idea of an ML-based race predictor, at least until I'm able to find more data.

But I'm still glad I did this project! It's been far too long since I did any machine learning work, and this was a fantastic way for me to re-introduce myself to the field. It gave me the idea for my next project, modeling the impact on weather on running and cycling performance - a task for which I've generated ample data through hundreds of hours of outdoor activity. In a narrow sense, building an ML-based race predictor was a failure. But working on it taught me what I needed to know for my next project.