菱形継承問題(ひしがたけいしょうもんだい、英: diamond problem)は、多重継承を伴うオブジェクト指向プログラミング言語において、クラス A を2つのクラス B と C が継承し、B と C の両方をクラス D が継承する際に発生するあいまいさを指す用語である。たとえば、クラス D にあるメソッドが A で定義された(かつ D においてオーバーライドされていない)メソッドを呼び出すとしたとき、B と C がそのメソッドを異なった形でオーバーライドしていたら、D は B と C のどちらのメソッドを継承するのか、という問題がある[1]。
C++ では、デフォルトでは個々の継承経路を独立して扱う。従って D オブジェクトには実際には2つの独立した A オブジェクトが内包され、A のメンバの使用は適切に行われる。A から B への継承と A から C への継承が共に "virtual"(例えば "class B : virtual public A")である場合、C++ はこれを特別に扱い、1つの A オブジェクトだけを生成し、A のメンバは正しく動作する。仮想継承と仮想でない継承が混在した場合、唯一の仮想の A と個々の仮想でない継承経路ごとの A が存在することになる。
Common Lisp では、合理的なデフォルトの動作とそれをオーバーライドする能力を提供する。デフォルトでは、引数のクラス指定が最も具体的なメソッドが選択され、サブクラスの定義内でスーパークラスが指定された順番に従う。しかし、プログラマはこれをオーバーライドでき、メソッドごとの解決順序を指定したり、メソッド結合規則を指定したりできる。
菱形問題は継承に限ったことではない。A、B、C、D というヘッダファイルが互いに菱形を形成するように "#include" されている場合、同様の問題が発生しうる。プリプロセッサで処理された結果、A にあった宣言が B と C で異なった形に変えられ、"#ifdef" が適切に機能しないという状況がありうる。同様に、ミドルウェアスタックでも似たような問題が発生する。A がデータベース、B と C がそのキャッシュだとした場合、D が B と C にトランザクションのコミットを要求すると、A にはコミット要求が重複して届いてしまう。