diff --git a/lib/util/cache/repository/types.ts b/lib/util/cache/repository/types.ts
index 5f1ce3679629d862a4b3dd66318807661cb83607..7ba2a0e3252ad16d5045ea6ec3ec21187cd50b9d 100644
--- a/lib/util/cache/repository/types.ts
+++ b/lib/util/cache/repository/types.ts
@@ -1,5 +1,6 @@
 import type { PackageFile } from '../../../manager/types';
 import type { RepoInitConfig } from '../../../workers/repository/init/types';
+import type { GitConflictsCache } from '../../git/types';
 
 export interface BaseBranchCache {
   sha: string; // branch commit sha
@@ -50,4 +51,5 @@ export interface Cache {
       graphqlPageCache?: Record<string, GithubGraphqlPageCache>;
     };
   };
+  gitConflicts?: GitConflictsCache;
 }
diff --git a/lib/util/git/conflicts-cache.spec.ts b/lib/util/git/conflicts-cache.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a6c8de8b80afe61a2239b264543a271181ab7b54
--- /dev/null
+++ b/lib/util/git/conflicts-cache.spec.ts
@@ -0,0 +1,141 @@
+import { mocked } from '../../../test/util';
+import * as _repositoryCache from '../../util/cache/repository';
+import type { Cache } from '../cache/repository/types';
+import {
+  getCachedConflictResult,
+  setCachedConflictResult,
+} from './conflicts-cache';
+
+jest.mock('../../util/cache/repository');
+const repositoryCache = mocked(_repositoryCache);
+
+describe('util/git/conflicts-cache', () => {
+  let repoCache: Cache = {};
+
+  beforeEach(() => {
+    repoCache = {};
+    repositoryCache.getCache.mockReturnValue(repoCache);
+  });
+
+  describe('getCachedConflictResult', () => {
+    it('returns null if cache is not populated', () => {
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeNull();
+    });
+
+    it('returns null if target key not found', () => {
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeNull();
+    });
+
+    it('returns null if target SHA has changed', () => {
+      repoCache.gitConflicts = {
+        foo: { targetBranchSha: 'aaa', sourceBranches: {} },
+      };
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeNull();
+    });
+
+    it('returns null if source key not found', () => {
+      repoCache.gitConflicts = {
+        foo: { targetBranchSha: '111', sourceBranches: {} },
+      };
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeNull();
+    });
+
+    it('returns null if source key has changed', () => {
+      repoCache.gitConflicts = {
+        foo: {
+          targetBranchSha: '111',
+          sourceBranches: {
+            bar: { sourceBranchSha: 'bbb', isConflicted: true },
+          },
+        },
+      };
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeNull();
+    });
+
+    it('returns true', () => {
+      repoCache.gitConflicts = {
+        foo: {
+          targetBranchSha: '111',
+          sourceBranches: {
+            bar: { sourceBranchSha: '222', isConflicted: true },
+          },
+        },
+      };
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeTrue();
+    });
+
+    it('returns false', () => {
+      repoCache.gitConflicts = {
+        foo: {
+          targetBranchSha: '111',
+          sourceBranches: {
+            bar: { sourceBranchSha: '222', isConflicted: false },
+          },
+        },
+      };
+      expect(getCachedConflictResult('foo', '111', 'bar', '222')).toBeFalse();
+    });
+  });
+
+  describe('setCachedConflictResult', () => {
+    it('sets value for unpopulated cache', () => {
+      setCachedConflictResult('foo', '111', 'bar', '222', true);
+      expect(repoCache).toEqual({
+        gitConflicts: {
+          foo: {
+            targetBranchSha: '111',
+            sourceBranches: {
+              bar: { sourceBranchSha: '222', isConflicted: true },
+            },
+          },
+        },
+      });
+    });
+
+    it('replaces value when source SHA has changed', () => {
+      setCachedConflictResult('foo', '111', 'bar', '222', false);
+      setCachedConflictResult('foo', '111', 'bar', '333', false);
+      setCachedConflictResult('foo', '111', 'bar', '444', true);
+      expect(repoCache).toEqual({
+        gitConflicts: {
+          foo: {
+            targetBranchSha: '111',
+            sourceBranches: {
+              bar: { sourceBranchSha: '444', isConflicted: true },
+            },
+          },
+        },
+      });
+    });
+
+    it('replaces value when target SHA has changed', () => {
+      setCachedConflictResult('foo', '111', 'bar', '222', false);
+      setCachedConflictResult('foo', 'aaa', 'bar', '222', true);
+      expect(repoCache).toEqual({
+        gitConflicts: {
+          foo: {
+            targetBranchSha: 'aaa',
+            sourceBranches: {
+              bar: { sourceBranchSha: '222', isConflicted: true },
+            },
+          },
+        },
+      });
+    });
+
+    it('replaces value when both target and source SHA have changed', () => {
+      setCachedConflictResult('foo', '111', 'bar', '222', true);
+      setCachedConflictResult('foo', 'aaa', 'bar', 'bbb', false);
+      expect(repoCache).toEqual({
+        gitConflicts: {
+          foo: {
+            targetBranchSha: 'aaa',
+            sourceBranches: {
+              bar: { sourceBranchSha: 'bbb', isConflicted: false },
+            },
+          },
+        },
+      });
+    });
+  });
+});
diff --git a/lib/util/git/conflicts-cache.ts b/lib/util/git/conflicts-cache.ts
new file mode 100644
index 0000000000000000000000000000000000000000..35e56d63255e717a71120e64d5fea8214d8c3655
--- /dev/null
+++ b/lib/util/git/conflicts-cache.ts
@@ -0,0 +1,56 @@
+import { getCache } from '../cache/repository';
+
+export function getCachedConflictResult(
+  targetBranchName: string,
+  targetBranchSha: string,
+  sourceBranchName: string,
+  sourceBranchSha: string
+): boolean | null {
+  const { gitConflicts } = getCache();
+  if (!gitConflicts) {
+    return null;
+  }
+
+  const targetBranchConflicts = gitConflicts[targetBranchName];
+  if (targetBranchConflicts?.targetBranchSha !== targetBranchSha) {
+    return null;
+  }
+
+  const sourceBranchConflict =
+    targetBranchConflicts.sourceBranches[sourceBranchName];
+  if (sourceBranchConflict?.sourceBranchSha !== sourceBranchSha) {
+    return null;
+  }
+
+  return sourceBranchConflict.isConflicted;
+}
+
+export function setCachedConflictResult(
+  targetBranchName: string,
+  targetBranchSha: string,
+  sourceBranchName: string,
+  sourceBranchSha: string,
+  isConflicted: boolean
+): void {
+  const cache = getCache();
+  cache.gitConflicts ??= {};
+  const { gitConflicts } = cache;
+
+  let targetBranchConflicts = gitConflicts[targetBranchName];
+  if (targetBranchConflicts?.targetBranchSha !== targetBranchSha) {
+    gitConflicts[targetBranchName] = {
+      targetBranchSha,
+      sourceBranches: {},
+    };
+    targetBranchConflicts = gitConflicts[targetBranchName];
+  }
+
+  const sourceBranchConflict =
+    targetBranchConflicts.sourceBranches[sourceBranchName];
+  if (sourceBranchConflict?.sourceBranchSha !== sourceBranchSha) {
+    targetBranchConflicts.sourceBranches[sourceBranchName] = {
+      sourceBranchSha,
+      isConflicted,
+    };
+  }
+}
diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts
index 82168a7f0661bd1aef1f20f31f44c72771a20eec..7387019ab7ac911b9a29e1a12fb11822a1dfe649 100644
--- a/lib/util/git/index.spec.ts
+++ b/lib/util/git/index.spec.ts
@@ -1,12 +1,17 @@
 import fs from 'fs-extra';
 import Git from 'simple-git';
 import tmp from 'tmp-promise';
+import { mocked } from '../../../test/util';
 import { GlobalConfig } from '../../config/global';
 import { CONFIG_VALIDATION } from '../../constants/error-messages';
+import * as _conflictsCache from './conflicts-cache';
 import type { FileChange } from './types';
 import * as git from '.';
 import { setNoVerify } from '.';
 
+jest.mock('./conflicts-cache');
+const conflictsCache = mocked(_conflictsCache);
+
 // Class is no longer exported
 const SimpleGit = Git().constructor as { prototype: ReturnType<typeof Git> };
 
@@ -631,6 +636,8 @@ describe('util/git/index', () => {
       await repo.commit('other (updated branch) message');
 
       await repo.checkout(defaultBranch);
+
+      conflictsCache.getCachedConflictResult.mockReturnValue(null);
     });
 
     it('returns true for non-existing source branch', async () => {
@@ -680,5 +687,70 @@ describe('util/git/index', () => {
       expect(status.current).toEqual(branchBefore);
       expect(status.isClean()).toBeTrue();
     });
+
+    describe('cache', () => {
+      beforeEach(() => {
+        jest.resetAllMocks();
+      });
+      it('returns cached values', async () => {
+        conflictsCache.getCachedConflictResult.mockReturnValue(true);
+
+        const res = await git.isBranchConflicted(
+          defaultBranch,
+          'renovate/conflicted_branch'
+        );
+
+        expect(res).toBeTrue();
+        expect(conflictsCache.getCachedConflictResult.mock.calls).toEqual([
+          [
+            defaultBranch,
+            expect.any(String),
+            'renovate/conflicted_branch',
+            expect.any(String),
+          ],
+        ]);
+        expect(conflictsCache.setCachedConflictResult).not.toHaveBeenCalled();
+      });
+
+      it('caches truthy return value', async () => {
+        conflictsCache.getCachedConflictResult.mockReturnValue(null);
+
+        const res = await git.isBranchConflicted(
+          defaultBranch,
+          'renovate/conflicted_branch'
+        );
+
+        expect(res).toBeTrue();
+        expect(conflictsCache.setCachedConflictResult.mock.calls).toEqual([
+          [
+            defaultBranch,
+            expect.any(String),
+            'renovate/conflicted_branch',
+            expect.any(String),
+            true,
+          ],
+        ]);
+      });
+
+      it('caches falsy return value', async () => {
+        conflictsCache.getCachedConflictResult.mockReturnValue(null);
+
+        const res = await git.isBranchConflicted(
+          defaultBranch,
+          'renovate/non_conflicted_branch'
+        );
+
+        expect(res).toBeFalse();
+        expect(conflictsCache.setCachedConflictResult.mock.calls).toEqual([
+          [
+            defaultBranch,
+            expect.any(String),
+            'renovate/non_conflicted_branch',
+            expect.any(String),
+            false,
+          ],
+        ]);
+      });
+    });
   });
 });
diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts
index c019a3c5b27ba286db26c8f7d566a3cf74acacef..09ea3d52b8e746c56e9eed92be2fdf4058aebbc1 100644
--- a/lib/util/git/index.ts
+++ b/lib/util/git/index.ts
@@ -1,4 +1,5 @@
 import URL from 'url';
+import is from '@sindresorhus/is';
 import fs from 'fs-extra';
 import simpleGit, {
   Options,
@@ -26,6 +27,10 @@ import { Limit, incLimitedValue } from '../../workers/global/limits';
 import { regEx } from '../regex';
 import { parseGitAuthor } from './author';
 import { getNoVerify, simpleGitConfig } from './config';
+import {
+  getCachedConflictResult,
+  setCachedConflictResult,
+} from './conflicts-cache';
 import { checkForPlatformFailure, handleCommitError } from './error';
 import { configSigningKey, writePrivateKey } from './private-key';
 import type {
@@ -518,7 +523,10 @@ export async function isBranchConflicted(
   logger.debug(`isBranchConflicted(${baseBranch}, ${branch})`);
   await syncGit();
   await writeGitAuthor();
-  if (!branchExists(baseBranch) || !branchExists(branch)) {
+
+  const baseBranchSha = getBranchCommit(baseBranch);
+  const branchSha = getBranchCommit(branch);
+  if (!baseBranchSha || !branchSha) {
     logger.warn(
       { baseBranch, branch },
       'isBranchConflicted: branch does not exist'
@@ -526,6 +534,19 @@ export async function isBranchConflicted(
     return true;
   }
 
+  const cachedResult = getCachedConflictResult(
+    baseBranch,
+    baseBranchSha,
+    branch,
+    branchSha
+  );
+  if (is.boolean(cachedResult)) {
+    logger.debug(
+      `Using cached result ${cachedResult} for isBranchConflicted(${baseBranch}, ${branch})`
+    );
+    return cachedResult;
+  }
+
   let result = false;
 
   const origBranch = config.currentBranch;
@@ -558,6 +579,7 @@ export async function isBranchConflicted(
     }
   }
 
+  setCachedConflictResult(baseBranch, baseBranchSha, branch, branchSha, result);
   return result;
 }
 
diff --git a/lib/util/git/types.ts b/lib/util/git/types.ts
index d0f82cfcfda5edd0398baa6878594e05f8fe641c..c2e1519a669d19a80075ceeebabac08b78e92a1b 100644
--- a/lib/util/git/types.ts
+++ b/lib/util/git/types.ts
@@ -75,6 +75,22 @@ export interface CommitFilesConfig {
   force?: boolean;
 }
 
+export type BranchName = string;
+export type TargetBranchName = BranchName;
+export type SourceBranchName = BranchName;
+
+export type GitConflictsCache = Record<TargetBranchName, TargetBranchConflicts>;
+
+export interface TargetBranchConflicts {
+  targetBranchSha: CommitSha;
+  sourceBranches: Record<SourceBranchName, SourceBranchConflict>;
+}
+
+export interface SourceBranchConflict {
+  sourceBranchSha: CommitSha;
+  isConflicted: boolean;
+}
+
 export interface CommitResult {
   sha: string;
   files: FileChange[];