The Algerian stepped up to penalty spot with five minutes remaining, but Lukasz Fabianski guessed correctly to deny Mahrez and keep City waiting.
Pep Guardiola’s side were two goals down at the break and in huge trouble, after Jarrod Bowen ruthlessly exploited the space in behind City’s defence to take his Premier League tally to 12 for the season.
Jack Grealish halved the deficit before an own goal from Vladimir Coufal levelled things up, and Mahrez then fluffed his lines from 12 yards late on.
It leaves City four points clear at the top of the table, though Liverpool have a game in hand. While the title is still in their hands, there is likely to be work to do on the final day of the season.
The Hammers barely had a kick in the opening 20 minutes as City dominated possession, though Craig Dawson headed over a big chance early on when free at the back post.
Out of nowhere, West Ham took the lead against the run of play. Pablo Fornals played a simple ball over the top, Bowen’s run was perfectly timed and he then went round Ederson to finish from a tight angle.
City started to look ragged in response but gradually got their composure back, with Gabriel Jesus going close to equalising from just outside the box.
But on the verge of half-time, West Ham’s lead was doubled. Again Bowen made the run in behind, nobody went with him and he was in on goal. He took his time before drilling through Fernandinho’s legs and into the bottom corner.
Whatever Guardiola told his players at the break, it certainly had an impact. City started the second-half brilliantly and within four minutes had pulled one back, when Grealish’s volley flicked off Dawson and flew past Lukasz Fabianski.
The pressure was relentless as the visitors pinned West Ham back. Grealish and Jesus both had chances, before Fabianski made a good save at his near post to deny Bernardo Silva.
City were careless at the back though and should have been punished. Bowen opted to go for goal from a tight angle when Declan Rice was waiting at the back post, firing into the side-netting.
It was Michail Antonio who had the biggest chance to restore the two-goal advantage. Fernandinho’s terrible backpass put Antonio through on goal, but he sent a lobbed effort over Ederson and over the bar.
Minutes later, City were level. Riyad Mahrez swung a dangerous free-kick into the box, Coufal threw himself at it and flicked a header into the far corner of his own net.
Mark Noble was brought on for the final time at the London Stadium to a huge ovation, and the match looked to be heading for a draw until Dawson brought down Jesus in the box. After a VAR review, the penalty was given.
City will know in a week’s time just how costly that Mahrez miss proves to be.